Browse Source

UI: Implement bars and polar area widgets.

pull/10643/head
Igor Kulikov 2 years ago
parent
commit
d12c680e12
  1. 16
      application/src/main/data/json/system/widget_bundles/charts.json
  2. 30
      application/src/main/data/json/system/widget_types/bars.json
  3. 29
      application/src/main/data/json/system/widget_types/bars_deprecated.json
  4. 37
      application/src/main/data/json/system/widget_types/polar_area.json
  5. 25
      application/src/main/data/json/system/widget_types/polar_area_deprecated.json
  6. 18
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  7. 261
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.html
  8. 316
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-basic-config.component.ts
  9. 18
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html
  10. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/doughnut-basic-config.component.html
  11. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/pie-chart-basic-config.component.html
  12. 2
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/pie-chart-basic-config.component.ts
  13. 268
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.html
  14. 318
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts
  15. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html
  16. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.ts
  17. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html
  18. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts
  19. 75
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-widget.component.ts
  20. 78
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-widget.models.ts
  21. 26
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts
  22. 61
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.models.ts
  23. 173
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.ts
  24. 316
      ui-ngx/src/app/modules/home/components/widget/lib/chart/chart.models.ts
  25. 61
      ui-ngx/src/app/modules/home/components/widget/lib/chart/doughnut-widget.models.ts
  26. 484
      ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts
  27. 67
      ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.models.ts
  28. 64
      ui-ngx/src/app/modules/home/components/widget/lib/chart/pie-chart-widget.models.ts
  29. 67
      ui-ngx/src/app/modules/home/components/widget/lib/chart/pie-chart.models.ts
  30. 75
      ui-ngx/src/app/modules/home/components/widget/lib/chart/polar-area-widget.component.ts
  31. 82
      ui-ngx/src/app/modules/home/components/widget/lib/chart/polar-area-widget.models.ts
  32. 48
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts
  33. 5
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts
  34. 6
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-state.models.ts
  35. 5
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts
  36. 5
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.models.ts
  37. 688
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  38. 44
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  39. 169
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-widget-settings.component.html
  40. 160
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-widget-settings.component.ts
  41. 18
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html
  42. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/doughnut-widget-settings.component.html
  43. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/pie-chart-widget-settings.component.html
  44. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/pie-chart-widget-settings.component.ts
  45. 176
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.html
  46. 161
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts
  47. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html
  48. 14
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.ts
  49. 6
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html
  50. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html
  51. 17
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts
  52. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html
  53. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts
  54. 20
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-animation-settings.component.html
  55. 23
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-animation-settings.component.ts
  56. 31
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-bar-settings.component.html
  57. 72
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-bar-settings.component.ts
  58. 14
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html
  59. 52
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts
  60. 14
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html
  61. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.html
  62. 18
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts
  63. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html
  64. 19
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  65. 23
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  66. 10
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  67. 106
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -15,18 +15,20 @@
"state_chart",
"bar_chart_with_labels",
"range_chart",
"cards.aggregated_value_card",
"bars",
"pie",
"doughnut",
"horizontal_doughnut",
"polar_area",
"charts.radar_chart_js",
"charts.basic_timeseries",
"charts.state_chart",
"charts.timeseries_bars_flot",
"cards.aggregated_value_card",
"charts.bars",
"pie",
"charts.pie",
"charts.pie_chart_js",
"doughnut",
"horizontal_doughnut",
"charts.polar_area_chart_js",
"charts.radar_chart_js",
"charts.doughnut_chart_js"
"charts.doughnut_chart_js",
"charts.polar_area_chart_js"
]
}

30
application/src/main/data/json/system/widget_types/bars.json

File diff suppressed because one or more lines are too long

29
application/src/main/data/json/system/widget_types/bars_deprecated.json

@ -0,0 +1,29 @@
{
"fqn": "charts.bars",
"name": "Bars",
"deprecated": true,
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAA8FBMVEUhlvNMr1Bqamp5eXl7e3t8fHx9fX1+fn5/f3+AgICCgoKDg4OEhISGhoaHh4eKioqMjIyNjY2Ojo6QkJCRkZGSkpKWlpaXl5ebm5udnZ2enp6goKChoaGkpKSnp6epqamsrKyurq6xsbGzs7O1tbW2tra3t7e4uLi7u7u9vb3BwcHCwsLDw8PGxsbKysrNzc3Ozs7R0dHS0tLT09PZ2dna2trc3Nzd3d3e3t7g4ODh4eHj4+Pk5OTm5ubn5+fo6Ojp6enu7u7w8PDz8/P0Qzb09PT29vb39/f5+fn6+vr7+/v8/Pz9/f3+/v7/wQf///+dc+aLAAAAAWJLR0RPbmZBSQAAAcFJREFUeNrt3ds2AgEYhuHsaSOZbAvZi0r2YYjCJOW7/7txZhkcDNbM6h/vdwfPmlX/ybtmEorJErGCeJLadz3rkKPpZamaLTp925DHdFvSpKelU9uQ/cLKhtcdk7YqtiHruevtojch7ZZtQ0o1dcdfRqXNqm1IbVU3OaVamm/YhvQW5zIXOknnC5JUt7qEpE5fUv/5HVePy2UHAgQIECBAgAABAgQIECBxgrwGHBAgQIAAAQIECBAgQIAAAQIECJC/QRIBN0iQ+66voDMLuRp2fQWdVUhvNun6CjqrkJ0Dx/UVdEYhzXzfcX0FnVFIrlSZ2mx/LOiMQuqHh6k972NBZ/fv13G/KeiCQkIu4358EL8UdBafSGwuOxAgQIAAAQIECJDYQB4CDggQIECAAAECBAgQIECAAAECBAgQIECA/BrSufn0DjqjkEZmLXkWaUEXEmThXMeFSAu68H4j5b1IC7rQILfZTqQFXViQ1nRTL1EWdCFBnmYuJUVZ0IUEWR1xHKcXZUEXFDLwBR2XHQgQIECAAAEC5H9ChgIOCBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgxiBmv+L6Bl9pkxYph15gAAAAAElFTkSuQmCC",
"description": "Displays latest values of the attributes or time-series data for multiple entities as separate bars.",
"descriptor": {
"type": "latest",
"sizeX": 7,
"sizeY": 5,
"resources": [
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"
}
],
"templateHtml": "<canvas id=\"barChart\"></canvas>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showTooltip = utils.defaultValue(settings.showTooltip, true);\n \n Chart.defaults.global.tooltips.enabled = settings.showTooltip;\n \n var barData = {\n labels: [],\n datasets: []\n };\n \n for (var i = 0; i < self.ctx.datasources.length; i++) {\n var datasource = self.ctx.datasources[i];\n for (var d = 0; d < datasource.dataKeys.length; d++) {\n var dataKey = datasource.dataKeys[d];\n var units = dataKey.units && dataKey.units.length ? dataKey.units : self.ctx.units;\n units = units ? (' (' + units + ')') : '';\n var dataset = {\n label: dataKey.label + units,\n data: [0],\n backgroundColor: [dataKey.color],\n borderColor: [dataKey.color],\n borderWidth: 1\n }\n barData.datasets.push(dataset);\n }\n }\n\n var ctx = $('#barChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'bar',\n data: barData,\n options: {\n responsive: false,\n maintainAspectRatio: false,\n scales: {\n yAxes: [{\n ticks: {\n beginAtZero:true\n }\n }]\n }\n }\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var c = 0;\n for (var i = 0; i < self.ctx.chart.data.datasets.length; i++) {\n var dataset = self.ctx.chart.data.datasets[i];\n var cellData = self.ctx.data[i]; \n if (cellData.data.length > 0) {\n var decimals;\n if (typeof cellData.dataKey.decimals !== 'undefined' \n && cellData.dataKey.decimals !== null ) {\n decimals = cellData.dataKey.decimals; \n } else {\n decimals = self.ctx.decimals;\n }\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = self.ctx.utils.formatValue(tvPair[1], decimals);\n dataset.data[0] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\nself.onDestroy = function() {\n self.ctx.chart.destroy();\n self.ctx.chart = null;\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-chart-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars\"}"
},
"externalId": null,
"tags": [
"bar",
"bar chart"
]
}

37
application/src/main/data/json/system/widget_types/polar_area.json

File diff suppressed because one or more lines are too long

25
application/src/main/data/json/system/widget_types/polar_area_deprecated.json

File diff suppressed because one or more lines are too long

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

@ -120,6 +120,12 @@ import {
import {
PieChartBasicConfigComponent
} from '@home/components/widget/config/basic/chart/pie-chart-basic-config.component';
import {
BarChartBasicConfigComponent
} from '@home/components/widget/config/basic/chart/bar-chart-basic-config.component';
import {
PolarAreaChartBasicConfigComponent
} from '@home/components/widget/config/basic/chart/polar-area-chart-basic-config.component';
@NgModule({
declarations: [
@ -159,7 +165,9 @@ import {
ComparisonKeyRowComponent,
ComparisonKeysTableComponent,
StatusWidgetBasicConfigComponent,
PieChartBasicConfigComponent
PieChartBasicConfigComponent,
BarChartBasicConfigComponent,
PolarAreaChartBasicConfigComponent
],
imports: [
CommonModule,
@ -201,7 +209,9 @@ import {
ToggleButtonBasicConfigComponent,
TimeSeriesChartBasicConfigComponent,
StatusWidgetBasicConfigComponent,
PieChartBasicConfigComponent
PieChartBasicConfigComponent,
BarChartBasicConfigComponent,
PolarAreaChartBasicConfigComponent
]
})
export class BasicWidgetConfigModule {
@ -237,5 +247,7 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-toggle-button-basic-config': ToggleButtonBasicConfigComponent,
'tb-time-series-chart-basic-config': TimeSeriesChartBasicConfigComponent,
'tb-status-widget-basic-config': StatusWidgetBasicConfigComponent,
'tb-pie-chart-basic-config': PieChartBasicConfigComponent
'tb-pie-chart-basic-config': PieChartBasicConfigComponent,
'tb-bar-chart-basic-config': BarChartBasicConfigComponent,
'tb-polar-area-chart-basic-config': PolarAreaChartBasicConfigComponent
};

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

@ -0,0 +1,261 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ng-container [formGroup]="barChartWidgetConfigForm">
<tb-timewindow-config-panel *ngIf="displayTimewindowConfig"
[onlyHistoryTimewindow]="onlyHistoryTimewindow()"
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 }}"
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.card-appearance</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.card-title' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field fxFlex 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 space-between">
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
{{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-material-icon-select asBoxInput
iconClearButton
[color]="barChartWidgetConfigForm.get('iconColor').value"
formControlName="titleIcon">
</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" formControlName="sortSeries">
{{ 'widgets.latest-chart.sort-series' | translate }}
</mat-slide-toggle>
</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">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-appearance</div>
<tb-chart-bar-settings
formControlName="barSettings"
[series]="false">
</tb-chart-bar-settings>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-axis</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.chart.chart-axis.scale</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.chart.chart-axis.scale-min</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisMin"
type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.chart.chart-axis.scale-max</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisMax"
type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="axisTickLabelFont"
clearButton
disabledLineHeight
forceSizeUnit="px"
previewText="100">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="axisTickLabelColor">
</tb-color-input>
</div>
</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="Wind">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'legend.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendValueFont"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendValueColor">
</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 column-xs">
<div>{{ 'tooltip.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="tooltipValueType">
<mat-option *ngFor="let type of latestChartTooltipValueTypes" [value]="type">
{{ latestChartTooltipValueTypeTranslationMap.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipValueDecimals" 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="tooltipValueFont"
[previewText]="tooltipValuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipValueColor">
</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>
<tb-chart-animation-settings
formControlName="animation">
</tb-chart-animation-settings>
<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>

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

@ -0,0 +1,316 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } 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,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation,
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 { formatValue, isUndefined, mergeDeep } from '@core/utils';
import {
getTimewindowConfig,
setTimewindowConfig
} from '@home/components/widget/config/timewindow-config-panel.component';
import {
LatestChartTooltipValueType,
latestChartTooltipValueTypes,
latestChartTooltipValueTypeTranslations
} from '@home/components/widget/lib/chart/latest-chart.models';
import {
barChartWidgetDefaultSettings,
BarChartWidgetSettings
} from '@home/components/widget/lib/chart/bar-chart-widget.models';
@Component({
selector: 'tb-bar-chart-basic-config',
templateUrl: './bar-chart-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class BarChartBasicConfigComponent extends BasicWidgetConfigComponent {
public get datasource(): Datasource {
const datasources: Datasource[] = this.barChartWidgetConfigForm.get('datasources').value;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
public get displayTimewindowConfig(): boolean {
const datasources = this.barChartWidgetConfigForm.get('datasources').value;
return datasourcesHasAggregation(datasources);
}
public onlyHistoryTimewindow(): boolean {
const datasources = this.barChartWidgetConfigForm.get('datasources').value;
return datasourcesHasOnlyComparisonAggregation(datasources);
}
legendPositions = legendPositions;
legendPositionTranslationMap = legendPositionTranslationMap;
latestChartTooltipValueTypes = latestChartTooltipValueTypes;
latestChartTooltipValueTypeTranslationMap = latestChartTooltipValueTypeTranslations;
barChartWidgetConfigForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.barChartWidgetConfigForm;
}
protected defaultDataKeys(configData: WidgetConfigComponentData): DataKey[] {
return [{ name: 'windPower', label: 'Wind', type: DataKeyType.timeseries, units: '', decimals: 0, color: '#08872B' },
{ name: 'solarPower', label: 'Solar', type: DataKeyType.timeseries, units: '', decimals: 0, color: '#FF4D5A' },
{ name: 'hydroelectricPower', label: 'Hydroelectric', type: DataKeyType.timeseries, units: '', decimals: 0, color: '#FFDE30' }];
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: BarChartWidgetSettings = mergeDeep<BarChartWidgetSettings>({} as BarChartWidgetSettings,
barChartWidgetDefaultSettings, configData.config.settings as BarChartWidgetSettings);
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, []],
showTitleIcon: [configData.config.showTitleIcon, []],
titleIcon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []],
sortSeries: [settings.sortSeries, []],
units: [configData.config.units, []],
decimals: [configData.config.decimals, []],
barSettings: [settings.barSettings, []],
axisMin: [settings.axisMin, []],
axisMax: [settings.axisMax, []],
axisTickLabelFont: [settings.axisTickLabelFont, []],
axisTickLabelColor: [settings.axisTickLabelColor, []],
animation: [settings.animation, []],
showLegend: [settings.showLegend, []],
legendPosition: [settings.legendPosition, []],
legendLabelFont: [settings.legendLabelFont, []],
legendLabelColor: [settings.legendLabelColor, []],
legendValueFont: [settings.legendValueFont, []],
legendValueColor: [settings.legendValueColor, []],
showTooltip: [settings.showTooltip, []],
tooltipValueType: [settings.tooltipValueType, []],
tooltipValueDecimals: [settings.tooltipValueDecimals, []],
tooltipValueFont: [settings.tooltipValueFont, []],
tooltipValueColor: [settings.tooltipValueColor, []],
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.showTitleIcon;
this.widgetConfig.config.titleIcon = config.titleIcon;
this.widgetConfig.config.iconColor = config.iconColor;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.sortSeries = config.sortSeries;
this.widgetConfig.config.units = config.units;
this.widgetConfig.config.decimals = config.decimals;
this.widgetConfig.config.settings.barSettings = config.barSettings;
this.widgetConfig.config.settings.axisMin = config.axisMin;
this.widgetConfig.config.settings.axisMax = config.axisMax;
this.widgetConfig.config.settings.axisTickLabelFont = config.axisTickLabelFont;
this.widgetConfig.config.settings.axisTickLabelColor = config.axisTickLabelColor;
this.widgetConfig.config.settings.animation = config.animation;
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.legendValueFont = config.legendValueFont;
this.widgetConfig.config.settings.legendValueColor = config.legendValueColor;
this.widgetConfig.config.settings.showTooltip = config.showTooltip;
this.widgetConfig.config.settings.tooltipValueType = config.tooltipValueType;
this.widgetConfig.config.settings.tooltipValueDecimals = config.tooltipValueDecimals;
this.widgetConfig.config.settings.tooltipValueFont = config.tooltipValueFont;
this.widgetConfig.config.settings.tooltipValueColor = config.tooltipValueColor;
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', 'showTitleIcon', 'showLegend', 'showTooltip'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.barChartWidgetConfigForm.get('showTitle').value;
const showTitleIcon: boolean = this.barChartWidgetConfigForm.get('showTitleIcon').value;
const showLegend: boolean = this.barChartWidgetConfigForm.get('showLegend').value;
const showTooltip: boolean = this.barChartWidgetConfigForm.get('showTooltip').value;
if (showTitle) {
this.barChartWidgetConfigForm.get('title').enable();
this.barChartWidgetConfigForm.get('titleFont').enable();
this.barChartWidgetConfigForm.get('titleColor').enable();
this.barChartWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false});
if (showTitleIcon) {
this.barChartWidgetConfigForm.get('titleIcon').enable();
this.barChartWidgetConfigForm.get('iconColor').enable();
} else {
this.barChartWidgetConfigForm.get('titleIcon').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('showTitleIcon').disable({emitEvent: false});
this.barChartWidgetConfigForm.get('titleIcon').disable();
this.barChartWidgetConfigForm.get('iconColor').disable();
}
if (showLegend) {
this.barChartWidgetConfigForm.get('legendPosition').enable();
this.barChartWidgetConfigForm.get('legendLabelFont').enable();
this.barChartWidgetConfigForm.get('legendLabelColor').enable();
this.barChartWidgetConfigForm.get('legendValueFont').enable();
this.barChartWidgetConfigForm.get('legendValueColor').enable();
} else {
this.barChartWidgetConfigForm.get('legendPosition').disable();
this.barChartWidgetConfigForm.get('legendLabelFont').disable();
this.barChartWidgetConfigForm.get('legendLabelColor').disable();
this.barChartWidgetConfigForm.get('legendValueFont').disable();
this.barChartWidgetConfigForm.get('legendValueColor').disable();
}
if (showTooltip) {
this.barChartWidgetConfigForm.get('tooltipValueType').enable();
this.barChartWidgetConfigForm.get('tooltipValueDecimals').enable();
this.barChartWidgetConfigForm.get('tooltipValueFont').enable();
this.barChartWidgetConfigForm.get('tooltipValueColor').enable();
this.barChartWidgetConfigForm.get('tooltipBackgroundColor').enable();
this.barChartWidgetConfigForm.get('tooltipBackgroundBlur').enable();
} else {
this.barChartWidgetConfigForm.get('tooltipValueType').disable();
this.barChartWidgetConfigForm.get('tooltipValueDecimals').disable();
this.barChartWidgetConfigForm.get('tooltipValueFont').disable();
this.barChartWidgetConfigForm.get('tooltipValueColor').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 _valuePreviewFn(): string {
const units: string = this.barChartWidgetConfigForm.get('units').value;
const decimals: number = this.barChartWidgetConfigForm.get('decimals').value;
return formatValue(110, decimals, units, false);
}
private _tooltipValuePreviewFn(): string {
const tooltipValueType: LatestChartTooltipValueType = this.barChartWidgetConfigForm.get('tooltipValueType').value;
const decimals: number = this.barChartWidgetConfigForm.get('tooltipValueDecimals').value;
if (tooltipValueType === LatestChartTooltipValueType.percentage) {
return formatValue(35, decimals, '%', false);
} else {
const units: string = this.barChartWidgetConfigForm.get('units').value;
return formatValue(110, decimals, units, false);
}
}
}

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

@ -143,26 +143,26 @@
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showBarBorder">
{{ 'widgets.time-series-chart.series.bar.show-border' | translate }}
{{ 'widgets.chart.bar.show-border' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.bar.border-width</div>
<div translate>widgets.chart.bar.border-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="barBorderWidth" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.bar.border-radius</div>
<div translate>widgets.chart.bar.border-radius</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="barBorderRadius" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<tb-time-series-chart-fill-settings
<tb-chart-fill-settings
formControlName="barBackgroundSettings"
title="widgets.time-series-chart.series.background"
fillNoneTitle="widgets.time-series-chart.series.fill-type-solid">
</tb-time-series-chart-fill-settings>
title="widgets.chart.background"
fillNoneTitle="widgets.chart.fill-type-solid">
</tb-chart-fill-settings>
<tb-time-series-no-aggregation-bar-width-settings
stroked
formControlName="noAggregationBarWidthSettings">
@ -306,9 +306,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-echarts-animation-settings
<tb-chart-animation-settings
formControlName="animation">
</tb-echarts-animation-settings>
</tb-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">

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

@ -229,9 +229,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-echarts-animation-settings
<tb-chart-animation-settings
formControlName="animation">
</tb-echarts-animation-settings>
</tb-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">

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

@ -247,9 +247,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-echarts-animation-settings
<tb-chart-animation-settings
formControlName="animation">
</tb-echarts-animation-settings>
</tb-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">

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

@ -48,7 +48,7 @@ import {
import {
pieChartLabelPositions,
pieChartLabelPositionTranslations
} from '@home/components/widget/lib/chart/pie-chart.models';
} from '@home/components/widget/lib/chart/chart.models';
@Component({
selector: 'tb-pie-chart-basic-config',

268
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.html

@ -0,0 +1,268 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ng-container [formGroup]="polarAreaChartWidgetConfigForm">
<tb-timewindow-config-panel *ngIf="displayTimewindowConfig"
[onlyHistoryTimewindow]="onlyHistoryTimewindow()"
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 }}"
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.card-appearance</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.card-title' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="titleFont"
clearButton
[previewText]="polarAreaChartWidgetConfigForm.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 space-between">
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
{{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-material-icon-select asBoxInput
iconClearButton
[color]="polarAreaChartWidgetConfigForm.get('iconColor').value"
formControlName="titleIcon">
</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" formControlName="sortSeries">
{{ 'widgets.latest-chart.sort-series' | translate }}
</mat-slide-toggle>
</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">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-appearance</div>
<tb-chart-bar-settings
formControlName="barSettings"
pieLabelPosition
[series]="false">
</tb-chart-bar-settings>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.polar-area-chart.polar-axis</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.chart.chart-axis.scale</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.chart.chart-axis.scale-min</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisMin"
type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.chart.chart-axis.scale-max</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisMax"
type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="axisTickLabelFont"
clearButton
disabledLineHeight
forceSizeUnit="px"
previewText="100">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="axisTickLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.polar-area-chart.start-angle</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="angleAxisStartAngle" type="number" min="0" max="360" 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]="polarAreaChartWidgetConfigForm.get('showLegend').value" [disabled]="!polarAreaChartWidgetConfigForm.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="Wind">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'legend.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendValueFont"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendValueColor">
</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]="polarAreaChartWidgetConfigForm.get('showTooltip').value" [disabled]="!polarAreaChartWidgetConfigForm.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 column-xs">
<div>{{ 'tooltip.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="tooltipValueType">
<mat-option *ngFor="let type of latestChartTooltipValueTypes" [value]="type">
{{ latestChartTooltipValueTypeTranslationMap.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipValueDecimals" 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="tooltipValueFont"
[previewText]="tooltipValuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipValueColor">
</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>
<tb-chart-animation-settings
formControlName="animation">
</tb-chart-animation-settings>
<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>

318
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/polar-area-chart-basic-config.component.ts

@ -0,0 +1,318 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component } from '@angular/core';
import { 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,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation,
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 { formatValue, isUndefined, mergeDeep } from '@core/utils';
import {
getTimewindowConfig,
setTimewindowConfig
} from '@home/components/widget/config/timewindow-config-panel.component';
import {
LatestChartTooltipValueType,
latestChartTooltipValueTypes,
latestChartTooltipValueTypeTranslations
} from '@home/components/widget/lib/chart/latest-chart.models';
import {
polarAreaChartWidgetDefaultSettings,
PolarAreaChartWidgetSettings
} from '@home/components/widget/lib/chart/polar-area-widget.models';
@Component({
selector: 'tb-polar-area-chart-basic-config',
templateUrl: './polar-area-chart-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class PolarAreaChartBasicConfigComponent extends BasicWidgetConfigComponent {
public get datasource(): Datasource {
const datasources: Datasource[] = this.polarAreaChartWidgetConfigForm.get('datasources').value;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
public get displayTimewindowConfig(): boolean {
const datasources = this.polarAreaChartWidgetConfigForm.get('datasources').value;
return datasourcesHasAggregation(datasources);
}
public onlyHistoryTimewindow(): boolean {
const datasources = this.polarAreaChartWidgetConfigForm.get('datasources').value;
return datasourcesHasOnlyComparisonAggregation(datasources);
}
legendPositions = legendPositions;
legendPositionTranslationMap = legendPositionTranslationMap;
latestChartTooltipValueTypes = latestChartTooltipValueTypes;
latestChartTooltipValueTypeTranslationMap = latestChartTooltipValueTypeTranslations;
polarAreaChartWidgetConfigForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.polarAreaChartWidgetConfigForm;
}
protected defaultDataKeys(configData: WidgetConfigComponentData): DataKey[] {
return [{ name: 'windPower', label: 'Wind', type: DataKeyType.timeseries, units: '', decimals: 0, color: '#08872B' },
{ name: 'solarPower', label: 'Solar', type: DataKeyType.timeseries, units: '', decimals: 0, color: '#FF4D5A' },
{ name: 'hydroelectricPower', label: 'Hydroelectric', type: DataKeyType.timeseries, units: '', decimals: 0, color: '#FFDE30' }];
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: PolarAreaChartWidgetSettings = mergeDeep<PolarAreaChartWidgetSettings>({} as PolarAreaChartWidgetSettings,
polarAreaChartWidgetDefaultSettings, configData.config.settings as PolarAreaChartWidgetSettings);
this.polarAreaChartWidgetConfigForm = 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, []],
showTitleIcon: [configData.config.showTitleIcon, []],
titleIcon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []],
sortSeries: [settings.sortSeries, []],
units: [configData.config.units, []],
decimals: [configData.config.decimals, []],
barSettings: [settings.barSettings, []],
axisMin: [settings.axisMin, []],
axisMax: [settings.axisMax, []],
axisTickLabelFont: [settings.axisTickLabelFont, []],
axisTickLabelColor: [settings.axisTickLabelColor, []],
angleAxisStartAngle: [settings.angleAxisStartAngle, [Validators.min(0), Validators.max(360)]],
animation: [settings.animation, []],
showLegend: [settings.showLegend, []],
legendPosition: [settings.legendPosition, []],
legendLabelFont: [settings.legendLabelFont, []],
legendLabelColor: [settings.legendLabelColor, []],
legendValueFont: [settings.legendValueFont, []],
legendValueColor: [settings.legendValueColor, []],
showTooltip: [settings.showTooltip, []],
tooltipValueType: [settings.tooltipValueType, []],
tooltipValueDecimals: [settings.tooltipValueDecimals, []],
tooltipValueFont: [settings.tooltipValueFont, []],
tooltipValueColor: [settings.tooltipValueColor, []],
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.showTitleIcon;
this.widgetConfig.config.titleIcon = config.titleIcon;
this.widgetConfig.config.iconColor = config.iconColor;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.sortSeries = config.sortSeries;
this.widgetConfig.config.units = config.units;
this.widgetConfig.config.decimals = config.decimals;
this.widgetConfig.config.settings.barSettings = config.barSettings;
this.widgetConfig.config.settings.axisMin = config.axisMin;
this.widgetConfig.config.settings.axisMax = config.axisMax;
this.widgetConfig.config.settings.axisTickLabelFont = config.axisTickLabelFont;
this.widgetConfig.config.settings.axisTickLabelColor = config.axisTickLabelColor;
this.widgetConfig.config.settings.angleAxisStartAngle = config.angleAxisStartAngle;
this.widgetConfig.config.settings.animation = config.animation;
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.legendValueFont = config.legendValueFont;
this.widgetConfig.config.settings.legendValueColor = config.legendValueColor;
this.widgetConfig.config.settings.showTooltip = config.showTooltip;
this.widgetConfig.config.settings.tooltipValueType = config.tooltipValueType;
this.widgetConfig.config.settings.tooltipValueDecimals = config.tooltipValueDecimals;
this.widgetConfig.config.settings.tooltipValueFont = config.tooltipValueFont;
this.widgetConfig.config.settings.tooltipValueColor = config.tooltipValueColor;
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', 'showTitleIcon', 'showLegend', 'showTooltip'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.polarAreaChartWidgetConfigForm.get('showTitle').value;
const showTitleIcon: boolean = this.polarAreaChartWidgetConfigForm.get('showTitleIcon').value;
const showLegend: boolean = this.polarAreaChartWidgetConfigForm.get('showLegend').value;
const showTooltip: boolean = this.polarAreaChartWidgetConfigForm.get('showTooltip').value;
if (showTitle) {
this.polarAreaChartWidgetConfigForm.get('title').enable();
this.polarAreaChartWidgetConfigForm.get('titleFont').enable();
this.polarAreaChartWidgetConfigForm.get('titleColor').enable();
this.polarAreaChartWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false});
if (showTitleIcon) {
this.polarAreaChartWidgetConfigForm.get('titleIcon').enable();
this.polarAreaChartWidgetConfigForm.get('iconColor').enable();
} else {
this.polarAreaChartWidgetConfigForm.get('titleIcon').disable();
this.polarAreaChartWidgetConfigForm.get('iconColor').disable();
}
} else {
this.polarAreaChartWidgetConfigForm.get('title').disable();
this.polarAreaChartWidgetConfigForm.get('titleFont').disable();
this.polarAreaChartWidgetConfigForm.get('titleColor').disable();
this.polarAreaChartWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false});
this.polarAreaChartWidgetConfigForm.get('titleIcon').disable();
this.polarAreaChartWidgetConfigForm.get('iconColor').disable();
}
if (showLegend) {
this.polarAreaChartWidgetConfigForm.get('legendPosition').enable();
this.polarAreaChartWidgetConfigForm.get('legendLabelFont').enable();
this.polarAreaChartWidgetConfigForm.get('legendLabelColor').enable();
this.polarAreaChartWidgetConfigForm.get('legendValueFont').enable();
this.polarAreaChartWidgetConfigForm.get('legendValueColor').enable();
} else {
this.polarAreaChartWidgetConfigForm.get('legendPosition').disable();
this.polarAreaChartWidgetConfigForm.get('legendLabelFont').disable();
this.polarAreaChartWidgetConfigForm.get('legendLabelColor').disable();
this.polarAreaChartWidgetConfigForm.get('legendValueFont').disable();
this.polarAreaChartWidgetConfigForm.get('legendValueColor').disable();
}
if (showTooltip) {
this.polarAreaChartWidgetConfigForm.get('tooltipValueType').enable();
this.polarAreaChartWidgetConfigForm.get('tooltipValueDecimals').enable();
this.polarAreaChartWidgetConfigForm.get('tooltipValueFont').enable();
this.polarAreaChartWidgetConfigForm.get('tooltipValueColor').enable();
this.polarAreaChartWidgetConfigForm.get('tooltipBackgroundColor').enable();
this.polarAreaChartWidgetConfigForm.get('tooltipBackgroundBlur').enable();
} else {
this.polarAreaChartWidgetConfigForm.get('tooltipValueType').disable();
this.polarAreaChartWidgetConfigForm.get('tooltipValueDecimals').disable();
this.polarAreaChartWidgetConfigForm.get('tooltipValueFont').disable();
this.polarAreaChartWidgetConfigForm.get('tooltipValueColor').disable();
this.polarAreaChartWidgetConfigForm.get('tooltipBackgroundColor').disable();
this.polarAreaChartWidgetConfigForm.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 _valuePreviewFn(): string {
const units: string = this.polarAreaChartWidgetConfigForm.get('units').value;
const decimals: number = this.polarAreaChartWidgetConfigForm.get('decimals').value;
return formatValue(110, decimals, units, false);
}
private _tooltipValuePreviewFn(): string {
const tooltipValueType: LatestChartTooltipValueType = this.polarAreaChartWidgetConfigForm.get('tooltipValueType').value;
const decimals: number = this.polarAreaChartWidgetConfigForm.get('tooltipValueDecimals').value;
if (tooltipValueType === LatestChartTooltipValueType.percentage) {
return formatValue(35, decimals, '%', false);
} else {
const units: string = this.polarAreaChartWidgetConfigForm.get('units').value;
return formatValue(110, decimals, units, false);
}
}
}

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

@ -186,8 +186,8 @@
<div fxLayout="row" fxFlex.lt-md fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" fxFlex.lt-md appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointLabelPosition">
<mat-option *ngFor="let position of seriesLabelPositions" [value]="position">
{{ seriesLabelPositionTranslations.get(position) | translate }}
<mat-option *ngFor="let position of chartLabelPositions" [value]="position">
{{ chartLabelPositionTranslations.get(position) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
@ -216,8 +216,8 @@
<div translate>widgets.time-series-chart.series.point.point-shape</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointShape">
<mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ echartsShapeTranslations.get(shape) | translate }}
<mat-option *ngFor="let shape of chartShapes" [value]="shape">
{{ chartShapeTranslations.get(shape) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
@ -367,9 +367,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-echarts-animation-settings
<tb-chart-animation-settings
formControlName="animation">
</tb-echarts-animation-settings>
</tb-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">

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

@ -47,12 +47,10 @@ import {
import {
lineSeriesStepTypes,
lineSeriesStepTypeTranslations,
seriesLabelPositions,
seriesLabelPositionTranslations,
timeSeriesLineTypes,
timeSeriesLineTypeTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { echartsShapes, echartsShapeTranslations } from '@home/components/widget/lib/chart/echarts-widget.models';
import { chartLabelPositions, chartLabelPositionTranslations, chartShapes, chartShapeTranslations } from '@home/components/widget/lib/chart/chart.models';
@Component({
selector: 'tb-range-chart-basic-config',
@ -78,13 +76,13 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent {
timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations;
seriesLabelPositions = seriesLabelPositions;
chartLabelPositions = chartLabelPositions;
seriesLabelPositionTranslations = seriesLabelPositionTranslations;
chartLabelPositionTranslations = chartLabelPositionTranslations;
echartsShapes = echartsShapes;
chartShapes = chartShapes;
echartsShapeTranslations = echartsShapeTranslations;
chartShapeTranslations = chartShapeTranslations;
legendPositions = legendPositions;

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

@ -328,9 +328,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-echarts-animation-settings
<tb-chart-animation-settings
formControlName="animation">
</tb-echarts-animation-settings>
</tb-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">

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

@ -45,8 +45,8 @@ import {
timeSeriesChartWidgetDefaultSettings,
TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import { EChartsTooltipTrigger } from '@home/components/widget/lib/chart/echarts-widget.models';
import {
TimeSeriesChartTooltipTrigger,
TimeSeriesChartKeySettings,
TimeSeriesChartType,
TimeSeriesChartYAxes,
@ -76,7 +76,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
TimeSeriesChartType = TimeSeriesChartType;
EChartsTooltipTrigger = EChartsTooltipTrigger;
EChartsTooltipTrigger = TimeSeriesChartTooltipTrigger;
legendPositions = legendPositions;

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

@ -0,0 +1,75 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Input, OnInit, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
import { WidgetContext } from '@home/models/widget-component.models';
import { WidgetComponent } from '@home/components/widget/widget.component';
import { TranslateService } from '@ngx-translate/core';
import {
LatestChartComponent,
LatestChartComponentCallbacks
} from '@home/components/widget/lib/chart/latest-chart.component';
import {
barChartWidgetBarsChartSettings,
barChartWidgetDefaultSettings,
BarChartWidgetSettings
} from '@home/components/widget/lib/chart/bar-chart-widget.models';
import { TbBarsChart } from '@home/components/widget/lib/chart/bars-chart';
@Component({
selector: 'tb-bar-chart-widget',
templateUrl: './latest-chart-widget.component.html',
styleUrls: [],
encapsulation: ViewEncapsulation.None
})
export class BarChartWidgetComponent implements OnInit {
@ViewChild('latestChart')
latestChart: LatestChartComponent;
@Input()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
settings: BarChartWidgetSettings;
callbacks: LatestChartComponentCallbacks;
constructor(private widgetComponent: WidgetComponent,
private translate: TranslateService) {
}
ngOnInit(): void {
this.ctx.$scope.barChartWidget = this;
this.settings = {...barChartWidgetDefaultSettings, ...this.ctx.settings};
this.callbacks = {
createChart: (chartShape, renderer) => {
const settings = barChartWidgetBarsChartSettings(this.settings);
return new TbBarsChart(this.ctx, settings, chartShape.nativeElement, renderer, this.translate, true);
}
};
}
public onInit() {
this.latestChart?.onInit();
}
public onDataUpdated() {
this.latestChart?.onDataUpdated();
}
}

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

@ -0,0 +1,78 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
latestChartWidgetDefaultSettings,
LatestChartWidgetSettings
} from '@home/components/widget/lib/chart/latest-chart.models';
import { mergeDeep } from '@core/utils';
import {
barsChartAnimationDefaultSettings,
BarsChartSettings
} from '@home/components/widget/lib/chart/bars-chart.models';
import { Font } from '@shared/models/widget-settings.models';
import { DeepPartial } from '@shared/models/common';
import {
ChartAnimationSettings,
chartBarDefaultSettings,
ChartBarSettings,
chartColorScheme
} from '@home/components/widget/lib/chart/chart.models';
export interface BarChartWidgetSettings extends LatestChartWidgetSettings {
axisMin?: number;
axisMax?: number;
axisTickLabelFont: Font;
axisTickLabelColor: string;
barSettings: ChartBarSettings;
}
export const barChartWidgetDefaultSettings: BarChartWidgetSettings = {
...latestChartWidgetDefaultSettings,
animation: mergeDeep({} as ChartAnimationSettings,
barsChartAnimationDefaultSettings),
axisTickLabelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
axisTickLabelColor: chartColorScheme['axis.tickLabel'].light,
barSettings: mergeDeep({} as ChartBarSettings, chartBarDefaultSettings,
{barWidth: 80, showLabel: true} as ChartBarSettings)
};
export const barChartWidgetBarsChartSettings = (settings: BarChartWidgetSettings): DeepPartial<BarsChartSettings> => ({
polar: false,
axisMin: settings.axisMin,
axisMax: settings.axisMax,
axisTickLabelFont: settings.axisTickLabelFont,
axisTickLabelColor: settings.axisTickLabelColor,
barSettings: settings.barSettings,
sortSeries: settings.sortSeries,
showTotal: false,
animation: settings.animation,
showLegend: settings.showLegend,
showTooltip: settings.showTooltip,
tooltipValueType: settings.tooltipValueType,
tooltipValueDecimals: settings.tooltipValueDecimals,
tooltipValueFont: settings.tooltipValueFont,
tooltipValueColor: settings.tooltipValueColor,
tooltipBackgroundColor: settings.tooltipBackgroundColor,
tooltipBackgroundBlur: settings.tooltipBackgroundBlur
});

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

@ -23,17 +23,10 @@ import {
textStyle
} from '@shared/models/widget-settings.models';
import { LegendPosition } from '@shared/models/widget.models';
import {
echartsAnimationDefaultSettings,
EChartsAnimationSettings,
EChartsTooltipWidgetSettings
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { DeepPartial } from '@shared/models/common';
import {
defaultTimeSeriesChartXAxisSettings,
defaultTimeSeriesChartYAxisSettings,
SeriesFillSettings,
SeriesFillType,
timeSeriesChartGridDefaultSettings,
TimeSeriesChartGridSettings,
TimeSeriesChartKeySettings,
@ -42,14 +35,21 @@ import {
TimeSeriesChartSeriesType,
TimeSeriesChartSettings,
TimeSeriesChartThreshold,
TimeSeriesChartTooltipWidgetSettings,
TimeSeriesChartXAxisSettings,
TimeSeriesChartYAxisSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { CallbackDataParams, LabelLayoutOptionCallbackParams } from 'echarts/types/dist/shared';
import { formatValue, mergeDeep } from '@core/utils';
import { LabelLayoutOption } from 'echarts/types/src/util/types';
import {
chartAnimationDefaultSettings,
ChartAnimationSettings,
ChartFillSettings,
ChartFillType
} from '@home/components/widget/lib/chart/chart.models';
export interface BarChartWithLabelsWidgetSettings extends EChartsTooltipWidgetSettings {
export interface BarChartWithLabelsWidgetSettings extends TimeSeriesChartTooltipWidgetSettings {
dataZoom: boolean;
showBarLabel: boolean;
barLabelFont: Font;
@ -60,12 +60,12 @@ export interface BarChartWithLabelsWidgetSettings extends EChartsTooltipWidgetSe
showBarBorder: boolean;
barBorderWidth: number;
barBorderRadius: number;
barBackgroundSettings: SeriesFillSettings;
barBackgroundSettings: ChartFillSettings;
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
grid: TimeSeriesChartGridSettings;
yAxis: TimeSeriesChartYAxisSettings;
xAxis: TimeSeriesChartXAxisSettings;
animation: EChartsAnimationSettings;
animation: ChartAnimationSettings;
thresholds: TimeSeriesChartThreshold[];
showLegend: boolean;
legendPosition: LegendPosition;
@ -101,7 +101,7 @@ export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings
barBorderWidth: 2,
barBorderRadius: 0,
barBackgroundSettings: {
type: SeriesFillType.none,
type: ChartFillType.none,
opacity: 0.4,
gradient: {
start: 100,
@ -118,8 +118,8 @@ export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings
xAxis: mergeDeep({} as TimeSeriesChartXAxisSettings,
defaultTimeSeriesChartXAxisSettings,
{showTicks: false, showSplitLines: false} as TimeSeriesChartXAxisSettings),
animation: mergeDeep({} as EChartsAnimationSettings,
echartsAnimationDefaultSettings),
animation: mergeDeep({} as ChartAnimationSettings,
chartAnimationDefaultSettings),
thresholds: [],
showLegend: true,
legendPosition: LegendPosition.top,

61
ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.models.ts

@ -0,0 +1,61 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { latestChartDefaultSettings, LatestChartSettings } from '@home/components/widget/lib/chart/latest-chart.models';
import { mergeDeep } from '@core/utils';
import {
chartAnimationDefaultSettings,
ChartAnimationSettings,
chartBarDefaultSettings,
ChartBarSettings,
chartColorScheme
} from '@home/components/widget/lib/chart/chart.models';
import { Font } from '@shared/models/widget-settings.models';
export interface BarsChartSettings extends LatestChartSettings {
polar: boolean;
axisMin?: number | string;
axisMax?: number | string;
axisTickLabelFont: Font;
axisTickLabelColor: string;
angleAxisStartAngle?: number;
barSettings: ChartBarSettings;
}
export const barsChartAnimationDefaultSettings: ChartAnimationSettings =
mergeDeep({} as ChartAnimationSettings, chartAnimationDefaultSettings, {
animationDuration: 1000,
animationDurationUpdate: 500
} as ChartAnimationSettings);
export const barsChartDefaultSettings: BarsChartSettings = {
...latestChartDefaultSettings,
animation: mergeDeep({} as ChartAnimationSettings,
barsChartAnimationDefaultSettings),
polar: false,
axisTickLabelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
axisTickLabelColor: chartColorScheme['axis.tickLabel'].light,
angleAxisStartAngle: 0,
barSettings: mergeDeep({} as ChartBarSettings, chartBarDefaultSettings,
{barWidth: 80, showLabel: true} as ChartBarSettings)
};

173
ui-ngx/src/app/modules/home/components/widget/lib/chart/bars-chart.ts

@ -0,0 +1,173 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { TbLatestChart } from '@home/components/widget/lib/chart/latest-chart';
import { barsChartDefaultSettings, BarsChartSettings } from '@home/components/widget/lib/chart/bars-chart.models';
import { WidgetContext } from '@home/models/widget-component.models';
import { DeepPartial } from '@shared/models/common';
import { PieChartSettings } from '@home/components/widget/lib/chart/pie-chart.models';
import { Renderer2 } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { ComponentStyle } from '@shared/models/widget-settings.models';
import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient';
import tinycolor from 'tinycolor2';
import { BarDataItemOption, BarSeriesLabelOption } from 'echarts/types/src/chart/bar/BarSeries';
import { formatValue, isDefinedAndNotNull } from '@core/utils';
import {
ChartFillType,
ChartLabelPosition,
createChartTextStyle,
createLinearOpacityGradient
} from '@home/components/widget/lib/chart/chart.models';
import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes';
import { RadiusAxisOption, YAXisOption } from 'echarts/types/dist/shared';
export class TbBarsChart extends TbLatestChart<BarsChartSettings> {
constructor(ctx: WidgetContext,
inputSettings: DeepPartial<PieChartSettings>,
chartElement: HTMLElement,
renderer: Renderer2,
translate: TranslateService,
autoResize = true) {
super(ctx, inputSettings, chartElement, renderer, translate, autoResize);
}
protected defaultSettings(): BarsChartSettings {
return barsChartDefaultSettings;
}
protected prepareLatestChartOption() {
let labelStyle: ComponentStyle = {};
if (this.settings.barSettings.showLabel) {
labelStyle = createChartTextStyle(this.settings.barSettings.labelFont,
this.settings.barSettings.labelColor, false, 'series.label', false);
}
const labelOption: BarSeriesLabelOption = {
show: this.settings.barSettings.showLabel,
position: this.settings.barSettings.labelPosition,
formatter: (params) => `{label|${params.name}}`,
rich: {
label: labelStyle
}
};
if (this.settings.barSettings.enableLabelBackground) {
labelOption.backgroundColor = this.settings.barSettings.labelBackground;
labelOption.padding = [4, 5];
labelOption.borderRadius = 4;
}
this.latestChartOption.series = [
{
type: 'bar',
barWidth: isDefinedAndNotNull(this.settings.barSettings.barWidth) ? this.settings.barSettings.barWidth + '%' : undefined,
itemStyle: {
borderWidth: this.settings.barSettings.showBorder ? this.settings.barSettings.borderWidth : 0
},
emphasis: {
focus: 'self'
},
coordinateSystem: this.settings.polar ? 'polar' : 'cartesian2d',
label: labelOption,
animation: this.settings.animation.animation,
animationThreshold: this.settings.animation.animationThreshold,
animationDuration: this.settings.animation.animationDuration,
animationEasing: this.settings.animation.animationEasing,
animationDelay: this.settings.animation.animationDelay,
animationDurationUpdate: this.settings.animation.animationDurationUpdate,
animationEasingUpdate: this.settings.animation.animationEasingUpdate,
animationDelayUpdate: this.settings.animation.animationDelayUpdate
}
];
const axisTickLabelStyle = createChartTextStyle(this.settings.axisTickLabelFont,
this.settings.axisTickLabelColor, false, 'axis.tickLabel');
const valueAxis: ValueAxisBaseOption = {
type: 'value',
min: this.settings.axisMin,
max: this.settings.axisMax,
axisLabel: {
color: axisTickLabelStyle.color,
fontStyle: axisTickLabelStyle.fontStyle,
fontWeight: axisTickLabelStyle.fontWeight,
fontFamily: axisTickLabelStyle.fontFamily,
fontSize: axisTickLabelStyle.fontSize,
formatter: (value: any) => formatValue(value, this.decimals, this.units, false)
}
};
if (this.settings.polar) {
this.latestChartOption.polar = {
radius: '100%'
};
this.latestChartOption.radiusAxis = valueAxis as RadiusAxisOption;
this.latestChartOption.angleAxis = {
type: 'category',
data: [],
startAngle: this.settings.angleAxisStartAngle
};
} else {
let minTop = 0;
let minBottom = 0;
if (this.settings.barSettings.showLabel) {
if (this.settings.barSettings.labelPosition === ChartLabelPosition.top) {
minTop = this.settings.barSettings.labelFont.size;
} else if (this.settings.barSettings.labelPosition === ChartLabelPosition.bottom) {
minBottom = this.settings.barSettings.labelFont.size;
}
}
this.latestChartOption.grid = [{
containLabel: true,
top: minTop,
bottom: minBottom,
left: 0,
right: 0
}];
this.latestChartOption.xAxis = {
type: 'category',
data: []
};
this.latestChartOption.yAxis = valueAxis as YAXisOption;
}
}
protected doUpdateSeriesData() {
const seriesData: BarDataItemOption[] = [];
for (const dataItem of this.dataItems) {
if (dataItem.enabled && dataItem.hasValue) {
const barSettings = this.settings.barSettings;
let borderRadius: number[];
if (dataItem.value < 0) {
borderRadius = [0, 0, barSettings.borderRadius, barSettings.borderRadius];
} else {
borderRadius = [barSettings.borderRadius, barSettings.borderRadius, 0, 0];
}
let barColor: string | LinearGradientObject;
if (barSettings.backgroundSettings.type === ChartFillType.none) {
barColor = dataItem.dataKey.color;
} else if (barSettings.backgroundSettings.type === ChartFillType.opacity) {
barColor = tinycolor(dataItem.dataKey.color).setAlpha(barSettings.backgroundSettings.opacity).toRgbString();
} else {
barColor = createLinearOpacityGradient(dataItem.dataKey.color, barSettings.backgroundSettings.gradient);
}
seriesData.push(
{id: dataItem.id, value: dataItem.value, name: dataItem.dataKey.label,
itemStyle: {color: barColor, borderColor: dataItem.dataKey.color, borderRadius}}
);
}
}
this.latestChartOption.series[0].data = seriesData;
}
}

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

@ -0,0 +1,316 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { isNumber } from '@core/utils';
import { TbColorScheme } from '@shared/models/color.models';
import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient';
import tinycolor from 'tinycolor2';
import { ComponentStyle, Font, textStyle } from '@shared/models/widget-settings.models';
import { LabelFormatterCallback } from 'echarts';
import { LabelLayoutOption } from 'echarts/types/src/util/types';
import { LabelLayoutOptionCallback } from 'echarts/types/dist/shared';
import { BuiltinTextPosition } from 'zrender/src/core/types';
export const chartColorScheme: TbColorScheme = {
'threshold.line': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
},
'threshold.label': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
},
'axis.line': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.label': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.ticks': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.tickLabel': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.splitLine': {
light: 'rgba(0, 0, 0, 0.12)',
dark: '#484753'
},
'series.label': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
}
};
export enum ChartShape {
emptyCircle = 'emptyCircle',
circle = 'circle',
rect = 'rect',
roundRect = 'roundRect',
triangle = 'triangle',
diamond = 'diamond',
pin = 'pin',
arrow = 'arrow',
none = 'none'
}
export const chartShapes = Object.keys(ChartShape) as ChartShape[];
export const chartShapeTranslations = new Map<ChartShape, string>(
[
[ChartShape.emptyCircle, 'widgets.chart.shape-empty-circle'],
[ChartShape.circle, 'widgets.chart.shape-circle'],
[ChartShape.rect, 'widgets.chart.shape-rect'],
[ChartShape.roundRect, 'widgets.chart.shape-round-rect'],
[ChartShape.triangle, 'widgets.chart.shape-triangle'],
[ChartShape.diamond, 'widgets.chart.shape-diamond'],
[ChartShape.pin, 'widgets.chart.shape-pin'],
[ChartShape.arrow, 'widgets.chart.shape-arrow'],
[ChartShape.none, 'widgets.chart.shape-none']
]
);
export enum ChartAnimationEasing {
linear = 'linear',
quadraticIn = 'quadraticIn',
quadraticOut = 'quadraticOut',
quadraticInOut = 'quadraticInOut',
cubicIn = 'cubicIn',
cubicOut = 'cubicOut',
cubicInOut = 'cubicInOut',
quarticIn = 'quarticIn',
quarticOut = 'quarticOut',
quarticInOut = 'quarticInOut',
quinticIn = 'quinticIn',
quinticOut = 'quinticOut',
quinticInOut = 'quinticInOut',
sinusoidalIn = 'sinusoidalIn',
sinusoidalOut = 'sinusoidalOut',
sinusoidalInOut = 'sinusoidalInOut',
exponentialIn = 'exponentialIn',
exponentialOut = 'exponentialOut',
exponentialInOut = 'exponentialInOut',
circularIn = 'circularIn',
circularOut = 'circularOut',
circularInOut = 'circularInOut',
elasticIn = 'elasticIn',
elasticOut = 'elasticOut',
elasticInOut = 'elasticInOut',
backIn = 'backIn',
backOut = 'backOut',
backInOut = 'backInOut',
bounceIn = 'bounceIn',
bounceOut = 'bounceOut',
bounceInOut = 'bounceInOut'
}
export const chartAnimationEasings = Object.keys(ChartAnimationEasing) as ChartAnimationEasing[];
export enum ChartFillType {
none = 'none',
opacity = 'opacity',
gradient = 'gradient'
}
export const chartFillTypes = Object.keys(ChartFillType) as ChartFillType[];
export const chartFillTypeTranslations = new Map<ChartFillType, string>(
[
[ChartFillType.none, 'widgets.chart.fill-type-none'],
[ChartFillType.opacity, 'widgets.chart.fill-type-opacity'],
[ChartFillType.gradient, 'widgets.chart.fill-type-gradient']
]
);
export enum ChartLabelPosition {
top = 'top',
bottom = 'bottom'
}
export const chartLabelPositions = Object.keys(ChartLabelPosition) as ChartLabelPosition[];
export const chartLabelPositionTranslations = new Map<ChartLabelPosition, string>(
[
[ChartLabelPosition.top, 'widgets.chart.label-position-top'],
[ChartLabelPosition.bottom, 'widgets.chart.label-position-bottom']
]
);
export enum PieChartLabelPosition {
inside = 'inside',
outside = 'outside'
}
export const pieChartLabelPositions = Object.keys(PieChartLabelPosition) as PieChartLabelPosition[];
export const pieChartLabelPositionTranslations = new Map<PieChartLabelPosition, string>(
[
[PieChartLabelPosition.inside, 'widgets.chart.label-position-inside'],
[PieChartLabelPosition.outside, 'widgets.chart.label-position-outside']
]
);
export interface ChartAnimationSettings {
animation: boolean;
animationThreshold: number;
animationDuration: number;
animationEasing: ChartAnimationEasing;
animationDelay: number;
animationDurationUpdate: number;
animationEasingUpdate: ChartAnimationEasing;
animationDelayUpdate: number;
}
export const chartAnimationDefaultSettings: ChartAnimationSettings = {
animation: true,
animationThreshold: 2000,
animationDuration: 500,
animationEasing: ChartAnimationEasing.cubicOut,
animationDelay: 0,
animationDurationUpdate: 300,
animationEasingUpdate: ChartAnimationEasing.cubicOut,
animationDelayUpdate: 0
};
export interface ChartFillSettings {
type: ChartFillType;
opacity: number;
gradient: {
start: number;
end: number;
};
}
export interface ChartBarSettings {
showBorder: boolean;
borderWidth: number;
borderRadius: number;
barWidth?: number;
showLabel: boolean;
labelPosition: ChartLabelPosition | PieChartLabelPosition | BuiltinTextPosition;
labelFont: Font;
labelColor: string;
enableLabelBackground: boolean;
labelBackground: string;
labelFormatter?: string | LabelFormatterCallback;
labelLayout?: LabelLayoutOption | LabelLayoutOptionCallback;
additionalLabelOption?: {[key: string]: any};
backgroundSettings: ChartFillSettings;
}
export const chartBarDefaultSettings: ChartBarSettings = {
showBorder: false,
borderWidth: 2,
borderRadius: 0,
showLabel: false,
labelPosition: ChartLabelPosition.top,
labelFont: {
family: 'Roboto',
size: 11,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
labelColor: chartColorScheme['series.label'].light,
enableLabelBackground: false,
labelBackground: 'rgba(255,255,255,0.56)',
backgroundSettings: {
type: ChartFillType.none,
opacity: 0.4,
gradient: {
start: 100,
end: 0
}
}
};
type ChartShapeOffsetFunction = (size: number) => number;
const chartShapeOffsetFunctions = new Map<ChartShape, ChartShapeOffsetFunction>(
[
[ChartShape.emptyCircle, size => size / 2 + 1],
[ChartShape.circle, size => size / 2],
[ChartShape.rect, size => size / 2],
[ChartShape.roundRect, size => size / 2],
[ChartShape.triangle, size => size / 2],
[ChartShape.diamond, size => size / 2],
[ChartShape.pin, size => size],
[ChartShape.arrow, () => 0],
[ChartShape.none, () => 0],
]
);
export const measureSymbolOffset = (symbol: string, symbolSize: any): number => {
if (isNumber(symbolSize)) {
if (symbol) {
const offsetFunction = chartShapeOffsetFunctions.get(symbol as ChartShape);
if (offsetFunction) {
return offsetFunction(symbolSize);
} else {
return symbolSize / 2;
}
}
} else {
return 0;
}
};
export const createLinearOpacityGradient = (color: string, gradient: {start: number; end: number}): LinearGradientObject => ({
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: tinycolor(color).setAlpha(gradient.start / 100).toRgbString() // color at 0%
}, {
offset: 1, color: tinycolor(color).setAlpha(gradient.end / 100).toRgbString() // color at 100%
}],
global: false
});
export const createChartTextStyle = (font: Font, color: string, darkMode: boolean, colorKey?: string, fill = false): ComponentStyle => {
const style = textStyle(font);
delete style.lineHeight;
style.fontSize = font.size;
if (fill) {
style.fill = prepareChartThemeColor(color, darkMode, colorKey);
} else {
style.color = prepareChartThemeColor(color, darkMode, colorKey);
}
return style;
};
export const prepareChartThemeColor = (color: string, darkMode: boolean, colorKey?: string): string => {
if (darkMode) {
let colorInstance = tinycolor(color);
if (colorInstance.isDark()) {
if (colorKey && chartColorScheme[colorKey]) {
return chartColorScheme[colorKey].dark;
} else {
const rgb = colorInstance.toRgb();
colorInstance = tinycolor({r: 255 - rgb.r, g: 255 - rgb.g, b: 255 - rgb.b, a: rgb.a});
return colorInstance.toRgbString();
}
}
}
return color;
};

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

@ -14,16 +14,16 @@
/// limitations under the License.
///
import { BackgroundType, ColorSettings, constantColor, Font } from '@shared/models/widget-settings.models';
import { ColorSettings, constantColor, Font } from '@shared/models/widget-settings.models';
import { LegendPosition } from '@shared/models/widget.models';
import { pieChartAnimationDefaultSettings, PieChartSettings } from '@home/components/widget/lib/chart/pie-chart.models';
import { DeepPartial } from '@shared/models/common';
import {
LatestChartTooltipValueType,
latestChartWidgetDefaultSettings,
LatestChartWidgetSettings
} from '@home/components/widget/lib/chart/latest-chart.models';
import { mergeDeep } from '@core/utils';
import { EChartsAnimationSettings } from '@home/components/widget/lib/chart/echarts-widget.models';
import { ChartAnimationSettings } from '@home/components/widget/lib/chart/chart.models';
export enum DoughnutLayout {
default = 'default',
@ -61,10 +61,14 @@ export interface DoughnutWidgetSettings extends LatestChartWidgetSettings {
}
export const doughnutDefaultSettings = (horizontal: boolean): DoughnutWidgetSettings => ({
layout: DoughnutLayout.default,
...latestChartWidgetDefaultSettings,
autoScale: true,
clockwise: false,
sortSeries: false,
animation: mergeDeep({} as ChartAnimationSettings,
pieChartAnimationDefaultSettings),
legendPosition: horizontal ? LegendPosition.right : LegendPosition.bottom,
layout: DoughnutLayout.default,
clockwise: false,
totalValueFont: {
family: 'Roboto',
size: 24,
@ -73,52 +77,7 @@ export const doughnutDefaultSettings = (horizontal: boolean): DoughnutWidgetSett
weight: '500',
lineHeight: '1'
},
totalValueColor: constantColor('rgba(0, 0, 0, 0.87)'),
animation: mergeDeep({} as EChartsAnimationSettings,
pieChartAnimationDefaultSettings),
showLegend: true,
legendPosition: horizontal ? LegendPosition.right : LegendPosition.bottom,
legendLabelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '16px'
},
legendLabelColor: 'rgba(0, 0, 0, 0.38)',
legendValueFont: {
family: 'Roboto',
size: 14,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '20px'
},
legendValueColor: 'rgba(0, 0, 0, 0.87)',
showTooltip: true,
tooltipValueType: LatestChartTooltipValueType.percentage,
tooltipValueDecimals: 0,
tooltipValueFont: {
family: 'Roboto',
size: 13,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '16px'
},
tooltipValueColor: '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
}
}
totalValueColor: constantColor('rgba(0, 0, 0, 0.87)')
});
export const doughnutPieChartSettings = (settings: DoughnutWidgetSettings): DeepPartial<PieChartSettings> => ({

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

@ -17,7 +17,6 @@
import * as echarts from 'echarts/core';
import AxisModel from 'echarts/types/src/coord/cartesian/AxisModel';
import { estimateLabelUnionRect } from 'echarts/lib/coord/axisHelper';
import { isDefinedAndNotNull, isFunction, isNumber } from '@core/utils';
import {
DataZoomComponent,
DataZoomComponentOption,
@ -25,6 +24,8 @@ import {
GridComponentOption,
MarkLineComponent,
MarkLineComponentOption,
PolarComponent,
PolarComponentOption,
TooltipComponent,
TooltipComponentOption,
VisualMapComponent,
@ -42,21 +43,12 @@ import {
} from 'echarts/charts';
import { LabelLayout } from 'echarts/features';
import { CanvasRenderer, SVGRenderer } from 'echarts/renderers';
import { DataEntry, DataKey, DataSet, Datasource, FormattedData } from '@shared/models/widget.models';
import {
calculateAggIntervalWithWidgetTimeWindow,
Interval,
IntervalMath,
WidgetTimewindow
} from '@shared/models/time/time.models';
import { CallbackDataParams, TimeAxisBandWidthCalculator } from 'echarts/types/dist/shared';
import { Renderer2 } from '@angular/core';
import { DateFormatProcessor, DateFormatSettings, Font } from '@shared/models/widget-settings.models';
import { CallbackDataParams } from 'echarts/types/dist/shared';
import GlobalModel from 'echarts/types/src/model/Global';
import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D';
import SeriesModel from 'echarts/types/src/model/Series';
import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
import { TimeAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes';
import { measureSymbolOffset } from '@home/components/widget/lib/chart/chart.models';
class EChartsModule {
private initialized = false;
@ -69,6 +61,7 @@ class EChartsModule {
VisualMapComponent,
DataZoomComponent,
MarkLineComponent,
PolarComponent,
LineChart,
BarChart,
PieChart,
@ -90,6 +83,7 @@ export type EChartsOption = echarts.ComposeOption<
| VisualMapComponentOption
| DataZoomComponentOption
| MarkLineComponentOption
| PolarComponentOption
| LineSeriesOption
| CustomSeriesOption
| BarSeriesOption
@ -98,166 +92,6 @@ export type EChartsOption = echarts.ComposeOption<
export type ECharts = echarts.ECharts;
export type EChartsDataItem = [number, any, number, number];
export type NamedDataSet = {name: string; value: EChartsDataItem}[];
export type EChartsTooltipValueFormatFunction = (value: any, latestData: FormattedData, units?: string, decimals?: number) => string;
export type EChartsSeriesItem = {
id: string;
datasource: Datasource;
dataKey: DataKey;
data: NamedDataSet;
dataSet?: DataSet;
enabled: boolean;
units?: string;
decimals?: number;
latestData?: FormattedData;
tooltipValueFormatFunction?: EChartsTooltipValueFormatFunction;
comparisonItem?: boolean;
};
export enum EChartsShape {
emptyCircle = 'emptyCircle',
circle = 'circle',
rect = 'rect',
roundRect = 'roundRect',
triangle = 'triangle',
diamond = 'diamond',
pin = 'pin',
arrow = 'arrow',
none = 'none'
}
export const echartsShapes = Object.keys(EChartsShape) as EChartsShape[];
export const echartsShapeTranslations = new Map<EChartsShape, string>(
[
[EChartsShape.emptyCircle, 'widgets.time-series-chart.shape-empty-circle'],
[EChartsShape.circle, 'widgets.time-series-chart.shape-circle'],
[EChartsShape.rect, 'widgets.time-series-chart.shape-rect'],
[EChartsShape.roundRect, 'widgets.time-series-chart.shape-round-rect'],
[EChartsShape.triangle, 'widgets.time-series-chart.shape-triangle'],
[EChartsShape.diamond, 'widgets.time-series-chart.shape-diamond'],
[EChartsShape.pin, 'widgets.time-series-chart.shape-pin'],
[EChartsShape.arrow, 'widgets.time-series-chart.shape-arrow'],
[EChartsShape.none, 'widgets.time-series-chart.shape-none']
]
);
type EChartsShapeOffsetFunction = (size: number) => number;
export const timeSeriesChartShapeOffsetFunctions = new Map<EChartsShape, EChartsShapeOffsetFunction>(
[
[EChartsShape.emptyCircle, size => size / 2 + 1],
[EChartsShape.circle, size => size / 2],
[EChartsShape.rect, size => size / 2],
[EChartsShape.roundRect, size => size / 2],
[EChartsShape.triangle, size => size / 2],
[EChartsShape.diamond, size => size / 2],
[EChartsShape.pin, size => size],
[EChartsShape.arrow, () => 0],
[EChartsShape.none, () => 0],
]
);
export enum EChartsAnimationEasing {
linear = 'linear',
quadraticIn = 'quadraticIn',
quadraticOut = 'quadraticOut',
quadraticInOut = 'quadraticInOut',
cubicIn = 'cubicIn',
cubicOut = 'cubicOut',
cubicInOut = 'cubicInOut',
quarticIn = 'quarticIn',
quarticOut = 'quarticOut',
quarticInOut = 'quarticInOut',
quinticIn = 'quinticIn',
quinticOut = 'quinticOut',
quinticInOut = 'quinticInOut',
sinusoidalIn = 'sinusoidalIn',
sinusoidalOut = 'sinusoidalOut',
sinusoidalInOut = 'sinusoidalInOut',
exponentialIn = 'exponentialIn',
exponentialOut = 'exponentialOut',
exponentialInOut = 'exponentialInOut',
circularIn = 'circularIn',
circularOut = 'circularOut',
circularInOut = 'circularInOut',
elasticIn = 'elasticIn',
elasticOut = 'elasticOut',
elasticInOut = 'elasticInOut',
backIn = 'backIn',
backOut = 'backOut',
backInOut = 'backInOut',
bounceIn = 'bounceIn',
bounceOut = 'bounceOut',
bounceInOut = 'bounceInOut'
}
export const echartsAnimationEasings = Object.keys(EChartsAnimationEasing) as EChartsAnimationEasing[];
export interface EChartsAnimationSettings {
animation: boolean;
animationThreshold: number;
animationDuration: number;
animationEasing: EChartsAnimationEasing;
animationDelay: number;
animationDurationUpdate: number;
animationEasingUpdate: EChartsAnimationEasing;
animationDelayUpdate: number;
}
export const echartsAnimationDefaultSettings: EChartsAnimationSettings = {
animation: true,
animationThreshold: 2000,
animationDuration: 500,
animationEasing: EChartsAnimationEasing.cubicOut,
animationDelay: 0,
animationDurationUpdate: 300,
animationEasingUpdate: EChartsAnimationEasing.cubicOut,
animationDelayUpdate: 0
};
export const timeAxisBandWidthCalculator: TimeAxisBandWidthCalculator = (model) => {
let interval: number;
const axisOption = model.option;
const seriesDataIndices = axisOption.axisPointer?.seriesDataIndices;
if (seriesDataIndices?.length) {
const seriesDataIndex = seriesDataIndices[0];
const series = model.ecModel.getSeriesByIndex(seriesDataIndex.seriesIndex);
if (series) {
const values = series.getData().getValues(seriesDataIndex.dataIndex);
const start = values[2];
const end = values[3];
if (typeof start === 'number' && typeof end === 'number') {
interval = Math.max(end - start, 1);
}
}
}
if (!interval) {
const tbTimeWindow: WidgetTimewindow = (axisOption as any).tbTimeWindow;
if (isDefinedAndNotNull(tbTimeWindow)) {
if (axisOption.axisPointer?.value && typeof axisOption.axisPointer?.value === 'number') {
const intervalArray = calculateAggIntervalWithWidgetTimeWindow(tbTimeWindow, axisOption.axisPointer.value);
const start = intervalArray[0];
const end = intervalArray[1];
interval = Math.max(end - start, 1);
} else {
interval = IntervalMath.numberValue(tbTimeWindow.interval);
}
}
}
if (interval) {
const timeScale = model.axis.scale;
const axisExtent = model.axis.getExtent();
const dataExtent = timeScale.getExtent();
const size = Math.abs(axisExtent[1] - axisExtent[0]);
return interval * (size / (dataExtent[1] - dataExtent[0]));
}
};
export const getAxis = (chart: ECharts, mainType: string, axisId: string): Axis2D => {
const model: GlobalModel = (chart as any).getModel();
const models = model.queryComponents({mainType, id: axisId});
@ -298,21 +132,6 @@ const _calculateAxisSize = (axis: Axis2D): number => {
return size;
};
const measureSymbolOffset = (symbol: string, symbolSize: any): number => {
if (isNumber(symbolSize)) {
if (symbol) {
const offsetFunction = timeSeriesChartShapeOffsetFunctions.get(symbol as EChartsShape);
if (offsetFunction) {
return offsetFunction(symbolSize);
} else {
return symbolSize / 2;
}
}
} else {
return 0;
}
};
export const measureThresholdOffset = (chart: ECharts, axisId: string, thresholdId: string, value: any): [number, number] => {
const offset: [number, number] = [0,0];
const axis = getAxis(chart, 'yAxis', axisId);
@ -435,294 +254,3 @@ export const getFocusedSeriesIndex = (chart: ECharts): number => {
}
return -1;
};
export const toNamedData = (data: DataSet, valueConverter?: (value: any) => any): NamedDataSet => {
if (!data?.length) {
return [];
} else {
return data.map(d => {
const ts = isDefinedAndNotNull(d[2]) ? d[2][0] : d[0];
return {
name: ts + '',
value: toEChartsDataItem(d, valueConverter)
};
});
}
};
const minDataTs = (dataSet: NamedDataSet): number => dataSet.length ? dataSet.map(data =>
Number(data.name)).reduce((a, b) => Math.min(a, b)) : undefined;
const maxDataTs = (dataSet: NamedDataSet): number => dataSet.length ? dataSet.map(data =>
Number(data.name)).reduce((a, b) => Math.max(a, b)) : undefined;
export const adjustTimeAxisExtentToData = (timeAxisOption: TimeAxisBaseOption,
dataItems: EChartsSeriesItem[],
defaultMin: number,
defaultMax: number): void => {
let min: number;
let max: number;
for (const item of dataItems) {
if (item.enabled) {
const minTs = minDataTs(item.data);
if (typeof minTs !== 'undefined') {
min = (typeof min !== 'undefined') ? Math.min(min, minTs) : minTs;
}
const maxTs = maxDataTs(item.data);
if (typeof maxTs !== 'undefined') {
max = (typeof max !== 'undefined') ? Math.max(max, maxTs) : maxTs;
}
}
}
timeAxisOption.min = (typeof min !== 'undefined' && Math.abs(min - defaultMin) < 1000) ? min : defaultMin;
timeAxisOption.max = (typeof max !== 'undefined' && Math.abs(max - defaultMax) < 1000) ? max : defaultMax;
};
const toEChartsDataItem = (entry: DataEntry, valueConverter?: (value: any) => any): EChartsDataItem => {
const value = valueConverter ? valueConverter(entry[1]) : entry[1];
const item: EChartsDataItem = [entry[0], value, entry[0], entry[0]];
if (isDefinedAndNotNull(entry[2])) {
item[2] = entry[2][0];
item[3] = entry[2][1];
}
return item;
};
export enum EChartsTooltipTrigger {
point = 'point',
axis = 'axis'
}
export const tooltipTriggerTranslationMap = new Map<EChartsTooltipTrigger, string>(
[
[ EChartsTooltipTrigger.point, 'tooltip.trigger-point' ],
[ EChartsTooltipTrigger.axis, 'tooltip.trigger-axis' ]
]
);
export interface EChartsTooltipWidgetSettings {
showTooltip: boolean;
tooltipTrigger?: EChartsTooltipTrigger;
tooltipShowFocusedSeries?: boolean;
tooltipLabelFont: Font;
tooltipLabelColor: string;
tooltipValueFont: Font;
tooltipValueColor: string;
tooltipValueFormatter?: string | EChartsTooltipValueFormatFunction;
tooltipShowDate: boolean;
tooltipDateInterval?: boolean;
tooltipDateFormat: DateFormatSettings;
tooltipDateFont: Font;
tooltipDateColor: string;
tooltipBackgroundColor: string;
tooltipBackgroundBlur: number;
}
export const createTooltipValueFormatFunction =
(tooltipValueFormatter: string | EChartsTooltipValueFormatFunction): EChartsTooltipValueFormatFunction => {
let tooltipValueFormatFunction: EChartsTooltipValueFormatFunction;
if (isFunction(tooltipValueFormatter)) {
tooltipValueFormatFunction = tooltipValueFormatter as EChartsTooltipValueFormatFunction;
} else if (typeof tooltipValueFormatter === 'string' && tooltipValueFormatter.length) {
try {
tooltipValueFormatFunction =
new Function('value', 'latestData', tooltipValueFormatter) as EChartsTooltipValueFormatFunction;
} catch (e) {}
}
return tooltipValueFormatFunction;
};
export const echartsTooltipFormatter = (renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: EChartsTooltipWidgetSettings,
params: CallbackDataParams[] | CallbackDataParams,
valueFormatFunction: EChartsTooltipValueFormatFunction,
focusedSeriesIndex: number,
series?: EChartsSeriesItem[],
interval?: Interval): null | HTMLElement => {
const tooltipParams = mapTooltipParams(params, series, focusedSeriesIndex);
if (!tooltipParams.items.length && !tooltipParams.comparisonItems.length) {
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', '16px');
buildItemsTooltip(tooltipElement, tooltipParams.items, renderer, tooltipDateFormat, settings, valueFormatFunction, interval);
buildItemsTooltip(tooltipElement, tooltipParams.comparisonItems, renderer, tooltipDateFormat, settings, valueFormatFunction, interval);
return tooltipElement;
};
interface TooltipItem {
param: CallbackDataParams;
dataItem: EChartsSeriesItem;
}
interface TooltipParams {
items: TooltipItem[];
comparisonItems: TooltipItem[];
}
const buildItemsTooltip = (tooltipElement: HTMLElement,
items: TooltipItem[],
renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: EChartsTooltipWidgetSettings,
valueFormatFunction: EChartsTooltipValueFormatFunction,
interval?: Interval) => {
if (items.length) {
const tooltipItemsElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(tooltipItemsElement, 'display', 'flex');
renderer.setStyle(tooltipItemsElement, 'flex-direction', 'column');
renderer.setStyle(tooltipItemsElement, 'align-items', 'flex-start');
renderer.setStyle(tooltipItemsElement, 'gap', '4px');
renderer.appendChild(tooltipElement, tooltipItemsElement);
if (settings.tooltipShowDate) {
renderer.appendChild(tooltipItemsElement,
constructEchartsTooltipDateElement(renderer, tooltipDateFormat, settings, items[0].param, interval));
}
for (const item of items) {
renderer.appendChild(tooltipItemsElement,
constructEchartsTooltipSeriesElement(renderer, settings, item, valueFormatFunction));
}
}
};
const mapTooltipParams = (params: CallbackDataParams[] | CallbackDataParams,
series?: EChartsSeriesItem[],
focusedSeriesIndex?: number): TooltipParams => {
const result: TooltipParams = {
items: [],
comparisonItems: []
};
if (!params || Array.isArray(params) && !params[0]) {
return result;
}
const firstParam = Array.isArray(params) ? params[0] : params;
if (!firstParam.value) {
return result;
}
let seriesParams: CallbackDataParams = null;
if (Array.isArray(params) && focusedSeriesIndex > -1) {
seriesParams = params.find(param => param.seriesIndex === focusedSeriesIndex);
} else if (!Array.isArray(params)) {
seriesParams = params;
}
if (seriesParams) {
appendTooltipItem(result, seriesParams, series);
} else if (Array.isArray(params)) {
for (seriesParams of params) {
appendTooltipItem(result, seriesParams, series);
}
}
return result;
};
const appendTooltipItem = (tooltipParams: TooltipParams, seriesParams: CallbackDataParams, series?: EChartsSeriesItem[]) => {
const dataItem = series?.find(s => s.id === seriesParams.seriesId);
const tooltipItem: TooltipItem = {
param: seriesParams,
dataItem
};
if (dataItem?.comparisonItem) {
tooltipParams.comparisonItems.push(tooltipItem);
} else {
tooltipParams.items.push(tooltipItem);
}
};
const constructEchartsTooltipDateElement = (renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: EChartsTooltipWidgetSettings,
param: CallbackDataParams,
interval?: Interval): HTMLElement => {
const dateElement: HTMLElement = renderer.createElement('div');
let dateText: string;
const startTs = param.value[2];
const endTs = param.value[3];
if (settings.tooltipDateInterval && startTs && endTs && (endTs - 1) > startTs) {
const startDateText = tooltipDateFormat.update(startTs, interval);
const endDateText = tooltipDateFormat.update(endTs - 1, interval);
if (startDateText === endDateText) {
dateText = startDateText;
} else {
dateText = startDateText + ' - ' + endDateText;
}
} else {
const ts = param.value[0];
dateText = tooltipDateFormat.update(ts, interval);
}
renderer.appendChild(dateElement, renderer.createText(dateText));
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);
return dateElement;
};
const constructEchartsTooltipSeriesElement = (renderer: Renderer2,
settings: EChartsTooltipWidgetSettings,
item: TooltipItem,
valueFormatFunction: EChartsTooltipValueFormatFunction): 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', item.param.color);
renderer.appendChild(labelElement, circleElement);
const labelTextElement: HTMLElement = renderer.createElement('div');
renderer.appendChild(labelTextElement, renderer.createText(item.param.seriesName));
renderer.setStyle(labelTextElement, 'font-family', settings.tooltipLabelFont.family);
renderer.setStyle(labelTextElement, 'font-size', settings.tooltipLabelFont.size + settings.tooltipLabelFont.sizeUnit);
renderer.setStyle(labelTextElement, 'font-style', settings.tooltipLabelFont.style);
renderer.setStyle(labelTextElement, 'font-weight', settings.tooltipLabelFont.weight);
renderer.setStyle(labelTextElement, 'line-height', settings.tooltipLabelFont.lineHeight);
renderer.setStyle(labelTextElement, 'color', settings.tooltipLabelColor);
renderer.appendChild(labelElement, labelTextElement);
const valueElement: HTMLElement = renderer.createElement('div');
let formatFunction = valueFormatFunction;
let latestData: FormattedData;
let units = '';
let decimals = 0;
if (item.dataItem) {
if (item.dataItem.tooltipValueFormatFunction) {
formatFunction = item.dataItem.tooltipValueFormatFunction;
}
latestData = item.dataItem.latestData;
units = item.dataItem.units;
decimals = item.dataItem.decimals;
}
if (!latestData) {
latestData = {} as FormattedData;
}
const value = formatFunction(item.param.value[1], latestData, units, decimals);
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;
};

67
ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.models.ts

@ -15,11 +15,11 @@
///
import { DataKey, Datasource, LegendPosition } from '@shared/models/widget.models';
import { BackgroundSettings, Font } from '@shared/models/widget-settings.models';
import { BackgroundSettings, BackgroundType, Font } from '@shared/models/widget-settings.models';
import { Renderer2 } from '@angular/core';
import { CallbackDataParams } from 'echarts/types/dist/shared';
import { formatValue, isDefinedAndNotNull } from '@core/utils';
import { EChartsAnimationSettings } from '@home/components/widget/lib/chart/echarts-widget.models';
import { formatValue, isDefinedAndNotNull, mergeDeep } from '@core/utils';
import { chartAnimationDefaultSettings, ChartAnimationSettings } from '@home/components/widget/lib/chart/chart.models';
export interface LatestChartDataItem {
id: number;
@ -63,14 +63,40 @@ export interface LatestChartTooltipSettings {
tooltipBackgroundBlur: number;
}
export const latestChartTooltipDefaultSettings: LatestChartTooltipSettings = {
showTooltip: true,
tooltipValueType: LatestChartTooltipValueType.percentage,
tooltipValueDecimals: 0,
tooltipValueFont: {
family: 'Roboto',
size: 13,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '16px'
},
tooltipValueColor: 'rgba(0, 0, 0, 0.76)',
tooltipBackgroundColor: 'rgba(255, 255, 255, 0.76)',
tooltipBackgroundBlur: 4
};
export interface LatestChartSettings extends LatestChartTooltipSettings {
autoScale?: boolean;
sortSeries: boolean;
showTotal?: boolean;
showLegend: boolean;
animation: EChartsAnimationSettings;
animation: ChartAnimationSettings;
}
export const latestChartDefaultSettings: LatestChartSettings = {
...latestChartTooltipDefaultSettings,
autoScale: false,
sortSeries: false,
showTotal: false,
showLegend: true,
animation: mergeDeep({} as ChartAnimationSettings, chartAnimationDefaultSettings)
};
export interface LatestChartWidgetSettings extends LatestChartSettings {
legendPosition: LegendPosition;
legendLabelFont: Font;
@ -80,6 +106,39 @@ export interface LatestChartWidgetSettings extends LatestChartSettings {
background: BackgroundSettings;
}
export const latestChartWidgetDefaultSettings: LatestChartWidgetSettings = {
...latestChartDefaultSettings,
showLegend: true,
legendPosition: LegendPosition.bottom,
legendLabelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '16px'
},
legendLabelColor: 'rgba(0, 0, 0, 0.38)',
legendValueFont: {
family: 'Roboto',
size: 14,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '20px'
},
legendValueColor: 'rgba(0, 0, 0, 0.87)',
background: {
type: BackgroundType.color,
color: '#fff',
overlay: {
enabled: false,
color: 'rgba(255,255,255,0.72)',
blur: 3
}
}
};
export const latestChartTooltipFormatter = (renderer: Renderer2,
settings: LatestChartTooltipSettings,
params: CallbackDataParams,

64
ui-ngx/src/app/modules/home/components/widget/lib/chart/pie-chart-widget.models.ts

@ -15,19 +15,14 @@
///
import {
LatestChartTooltipValueType,
latestChartWidgetDefaultSettings,
LatestChartWidgetSettings
} from '@home/components/widget/lib/chart/latest-chart.models';
import { BackgroundType, Font } from '@shared/models/widget-settings.models';
import { LegendPosition } from '@shared/models/widget.models';
import { Font } from '@shared/models/widget-settings.models';
import { DeepPartial } from '@shared/models/common';
import {
pieChartAnimationDefaultSettings,
PieChartLabelPosition,
PieChartSettings
} from '@home/components/widget/lib/chart/pie-chart.models';
import { pieChartAnimationDefaultSettings, PieChartSettings } from '@home/components/widget/lib/chart/pie-chart.models';
import { isDefinedAndNotNull, mergeDeep } from '@core/utils';
import { EChartsAnimationSettings } from '@home/components/widget/lib/chart/echarts-widget.models';
import { ChartAnimationSettings, PieChartLabelPosition } from '@home/components/widget/lib/chart/chart.models';
export interface PieChartWidgetSettings extends LatestChartWidgetSettings {
showLabel: boolean;
@ -41,6 +36,9 @@ export interface PieChartWidgetSettings extends LatestChartWidgetSettings {
}
export const pieChartWidgetDefaultSettings: PieChartWidgetSettings = {
...latestChartWidgetDefaultSettings,
animation: mergeDeep({} as ChartAnimationSettings,
pieChartAnimationDefaultSettings),
showLabel: true,
labelPosition: PieChartLabelPosition.outside,
labelFont: {
@ -55,53 +53,7 @@ export const pieChartWidgetDefaultSettings: PieChartWidgetSettings = {
borderWidth: 0,
borderColor: '#000',
radius: 80,
clockwise: false,
sortSeries: false,
animation: mergeDeep({} as EChartsAnimationSettings,
pieChartAnimationDefaultSettings),
showLegend: true,
legendPosition: LegendPosition.bottom,
legendLabelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '16px'
},
legendLabelColor: 'rgba(0, 0, 0, 0.38)',
legendValueFont: {
family: 'Roboto',
size: 14,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '20px'
},
legendValueColor: 'rgba(0, 0, 0, 0.87)',
showTooltip: true,
tooltipValueType: LatestChartTooltipValueType.percentage,
tooltipValueDecimals: 0,
tooltipValueFont: {
family: 'Roboto',
size: 13,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '16px'
},
tooltipValueColor: '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
}
}
clockwise: false
};
export const pieChartWidgetPieChartSettings = (settings: PieChartWidgetSettings): DeepPartial<PieChartSettings> => ({

67
ui-ngx/src/app/modules/home/components/widget/lib/chart/pie-chart.models.ts

@ -15,29 +15,13 @@
///
import { ColorSettings, constantColor, Font } from '@shared/models/widget-settings.models';
import {
LatestChartSettings,
LatestChartTooltipValueType
} from '@home/components/widget/lib/chart/latest-chart.models';
import { latestChartDefaultSettings, LatestChartSettings } from '@home/components/widget/lib/chart/latest-chart.models';
import { mergeDeep } from '@core/utils';
import {
EChartsAnimationEasing,
EChartsAnimationSettings
} from '@home/components/widget/lib/chart/echarts-widget.models';
export enum PieChartLabelPosition {
outside = 'outside',
inside = 'inside'
}
export const pieChartLabelPositions = Object.keys(PieChartLabelPosition) as PieChartLabelPosition[];
export const pieChartLabelPositionTranslations = new Map<PieChartLabelPosition, string>(
[
[PieChartLabelPosition.outside, 'widgets.pie-chart.label-position-outside'],
[PieChartLabelPosition.inside, 'widgets.pie-chart.label-position-inside']
]
);
chartAnimationDefaultSettings,
ChartAnimationSettings,
PieChartLabelPosition
} from '@home/components/widget/lib/chart/chart.models';
export interface PieChartSettings extends LatestChartSettings {
doughnut: boolean;
@ -59,27 +43,19 @@ export interface PieChartSettings extends LatestChartSettings {
emphasisShadowColor: string;
}
export const pieChartAnimationDefaultSettings: EChartsAnimationSettings = {
animation: true,
animationThreshold: 2000,
animationDuration: 1000,
animationEasing: EChartsAnimationEasing.cubicOut,
animationDelay: 0,
animationDurationUpdate: 500,
animationEasingUpdate: EChartsAnimationEasing.cubicOut,
animationDelayUpdate: 0
};
export const pieChartAnimationDefaultSettings: ChartAnimationSettings =
mergeDeep({} as ChartAnimationSettings, chartAnimationDefaultSettings, {
animationDuration: 1000,
animationDurationUpdate: 500
} as ChartAnimationSettings);
export const pieChartDefaultSettings: PieChartSettings = {
autoScale: false,
...latestChartDefaultSettings,
animation: mergeDeep({} as ChartAnimationSettings,
pieChartAnimationDefaultSettings),
doughnut: false,
radius: '80%',
clockwise: false,
sortSeries: false,
showTotal: false,
animation: mergeDeep({} as EChartsAnimationSettings,
pieChartAnimationDefaultSettings),
showLegend: true,
totalValueFont: {
family: 'Roboto',
size: 24,
@ -107,20 +83,5 @@ export const pieChartDefaultSettings: PieChartSettings = {
emphasisBorderWidth: 0,
emphasisBorderColor: '#000',
emphasisShadowBlur: 10,
emphasisShadowColor: 'rgba(0, 0, 0, 0.5)',
showTooltip: true,
tooltipValueType: LatestChartTooltipValueType.percentage,
tooltipValueDecimals: 0,
tooltipValueFont: {
family: 'Roboto',
size: 13,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '16px'
},
tooltipValueColor: 'rgba(0, 0, 0, 0.76)',
tooltipBackgroundColor: 'rgba(255, 255, 255, 0.76)',
tooltipBackgroundBlur: 4
emphasisShadowColor: 'rgba(0, 0, 0, 0.5)'
};

75
ui-ngx/src/app/modules/home/components/widget/lib/chart/polar-area-widget.component.ts

@ -0,0 +1,75 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Input, OnInit, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
import { WidgetContext } from '@home/models/widget-component.models';
import { WidgetComponent } from '@home/components/widget/widget.component';
import { TranslateService } from '@ngx-translate/core';
import {
LatestChartComponent,
LatestChartComponentCallbacks
} from '@home/components/widget/lib/chart/latest-chart.component';
import { TbBarsChart } from '@home/components/widget/lib/chart/bars-chart';
import {
polarAreaChartWidgetBarsChartSettings,
polarAreaChartWidgetDefaultSettings,
PolarAreaChartWidgetSettings
} from '@home/components/widget/lib/chart/polar-area-widget.models';
@Component({
selector: 'tb-polar-area-chart-widget',
templateUrl: './latest-chart-widget.component.html',
styleUrls: [],
encapsulation: ViewEncapsulation.None
})
export class PolarAreaWidgetComponent implements OnInit {
@ViewChild('latestChart')
latestChart: LatestChartComponent;
@Input()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
settings: PolarAreaChartWidgetSettings;
callbacks: LatestChartComponentCallbacks;
constructor(private widgetComponent: WidgetComponent,
private translate: TranslateService) {
}
ngOnInit(): void {
this.ctx.$scope.polarAreaChartWidget = this;
this.settings = {...polarAreaChartWidgetDefaultSettings, ...this.ctx.settings};
this.callbacks = {
createChart: (chartShape, renderer) => {
const settings = polarAreaChartWidgetBarsChartSettings(this.settings);
return new TbBarsChart(this.ctx, settings, chartShape.nativeElement, renderer, this.translate, true);
}
};
}
public onInit() {
this.latestChart?.onInit();
}
public onDataUpdated() {
this.latestChart?.onDataUpdated();
}
}

82
ui-ngx/src/app/modules/home/components/widget/lib/chart/polar-area-widget.models.ts

@ -0,0 +1,82 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
latestChartWidgetDefaultSettings,
LatestChartWidgetSettings
} from '@home/components/widget/lib/chart/latest-chart.models';
import { Font } from '@shared/models/widget-settings.models';
import {
ChartAnimationSettings,
chartBarDefaultSettings,
ChartBarSettings,
chartColorScheme,
PieChartLabelPosition
} from '@home/components/widget/lib/chart/chart.models';
import { mergeDeep } from '@core/utils';
import {
barsChartAnimationDefaultSettings,
BarsChartSettings
} from '@home/components/widget/lib/chart/bars-chart.models';
import { DeepPartial } from '@shared/models/common';
export interface PolarAreaChartWidgetSettings extends LatestChartWidgetSettings {
axisMin?: number;
axisMax?: number;
axisTickLabelFont: Font;
axisTickLabelColor: string;
angleAxisStartAngle?: number;
barSettings: ChartBarSettings;
}
export const polarAreaChartWidgetDefaultSettings: PolarAreaChartWidgetSettings = {
...latestChartWidgetDefaultSettings,
animation: mergeDeep({} as ChartAnimationSettings,
barsChartAnimationDefaultSettings),
axisTickLabelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
axisTickLabelColor: chartColorScheme['axis.tickLabel'].light,
angleAxisStartAngle: 90,
barSettings: mergeDeep({} as ChartBarSettings, chartBarDefaultSettings,
{barWidth: 100, showLabel: true, labelPosition: PieChartLabelPosition.outside} as ChartBarSettings)
};
export const polarAreaChartWidgetBarsChartSettings = (settings: PolarAreaChartWidgetSettings): DeepPartial<BarsChartSettings> => ({
polar: true,
axisMin: settings.axisMin,
axisMax: settings.axisMax,
axisTickLabelFont: settings.axisTickLabelFont,
axisTickLabelColor: settings.axisTickLabelColor,
angleAxisStartAngle: settings.angleAxisStartAngle,
barSettings: settings.barSettings,
sortSeries: settings.sortSeries,
showTotal: false,
animation: settings.animation,
showLegend: settings.showLegend,
showTooltip: settings.showTooltip,
tooltipValueType: settings.tooltipValueType,
tooltipValueDecimals: settings.tooltipValueDecimals,
tooltipValueFont: settings.tooltipValueFont,
tooltipValueColor: settings.tooltipValueColor,
tooltipBackgroundColor: settings.tooltipBackgroundColor,
tooltipBackgroundBlur: settings.tooltipBackgroundBlur
});

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

@ -24,32 +24,36 @@ import {
sortedColorRange
} from '@shared/models/widget-settings.models';
import { LegendPosition } from '@shared/models/widget.models';
import {
echartsAnimationDefaultSettings,
EChartsAnimationSettings,
EChartsShape,
EChartsTooltipWidgetSettings
} from '@home/components/widget/lib/chart/echarts-widget.models';
import {
createTimeSeriesChartVisualMapPiece,
defaultTimeSeriesChartXAxisSettings,
defaultTimeSeriesChartYAxisSettings,
LineSeriesStepType,
SeriesFillType,
SeriesLabelPosition, ThresholdLabelPosition,
timeSeriesChartColorScheme, timeSeriesChartGridDefaultSettings, TimeSeriesChartGridSettings,
ThresholdLabelPosition,
timeSeriesChartGridDefaultSettings,
TimeSeriesChartGridSettings,
TimeSeriesChartKeySettings,
TimeSeriesChartLineType,
TimeSeriesChartSeriesType,
TimeSeriesChartSettings,
TimeSeriesChartThreshold, timeSeriesChartThresholdDefaultSettings,
TimeSeriesChartThreshold,
timeSeriesChartThresholdDefaultSettings,
TimeSeriesChartThresholdType,
TimeSeriesChartTooltipWidgetSettings,
TimeSeriesChartVisualMapPiece,
TimeSeriesChartXAxisSettings,
TimeSeriesChartYAxisSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { isNumber, mergeDeep } from '@core/utils';
import { DeepPartial } from '@shared/models/common';
import {
chartAnimationDefaultSettings,
ChartAnimationSettings,
chartColorScheme,
ChartFillType,
ChartLabelPosition,
ChartShape
} from '@home/components/widget/lib/chart/chart.models';
export interface RangeItem {
index: number;
@ -62,7 +66,7 @@ export interface RangeItem {
piece: TimeSeriesChartVisualMapPiece;
}
export interface RangeChartWidgetSettings extends EChartsTooltipWidgetSettings {
export interface RangeChartWidgetSettings extends TimeSeriesChartTooltipWidgetSettings {
dataZoom: boolean;
rangeColors: Array<ColorRange>;
outOfRangeColor: string;
@ -78,17 +82,17 @@ export interface RangeChartWidgetSettings extends EChartsTooltipWidgetSettings {
lineWidth: number;
showPoints: boolean;
showPointLabel: boolean;
pointLabelPosition: SeriesLabelPosition;
pointLabelPosition: ChartLabelPosition;
pointLabelFont: Font;
pointLabelColor: string;
enablePointLabelBackground: boolean;
pointLabelBackground: string;
pointShape: EChartsShape;
pointShape: ChartShape;
pointSize: number;
grid: TimeSeriesChartGridSettings;
yAxis: TimeSeriesChartYAxisSettings;
xAxis: TimeSeriesChartXAxisSettings;
animation: EChartsAnimationSettings;
animation: ChartAnimationSettings;
thresholds: TimeSeriesChartThreshold[];
showLegend: boolean;
legendPosition: LegendPosition;
@ -115,9 +119,9 @@ export const rangeChartDefaultSettings: RangeChartWidgetSettings = {
timeSeriesChartThresholdDefaultSettings,
{ lineColor: '#37383b',
lineType: TimeSeriesChartLineType.dashed,
startSymbol: EChartsShape.circle,
startSymbol: ChartShape.circle,
startSymbolSize: 5,
endSymbol: EChartsShape.arrow,
endSymbol: ChartShape.arrow,
endSymbolSize: 7,
labelPosition: ThresholdLabelPosition.insideEndTop,
labelColor: '#37383b',
@ -132,7 +136,7 @@ export const rangeChartDefaultSettings: RangeChartWidgetSettings = {
lineWidth: 2,
showPoints: false,
showPointLabel: false,
pointLabelPosition: SeriesLabelPosition.top,
pointLabelPosition: ChartLabelPosition.top,
pointLabelFont: {
family: 'Roboto',
size: 11,
@ -141,10 +145,10 @@ export const rangeChartDefaultSettings: RangeChartWidgetSettings = {
weight: '400',
lineHeight: '1'
},
pointLabelColor: timeSeriesChartColorScheme['series.label'].light,
pointLabelColor: chartColorScheme['series.label'].light,
enablePointLabelBackground: false,
pointLabelBackground: 'rgba(255,255,255,0.56)',
pointShape: EChartsShape.emptyCircle,
pointShape: ChartShape.emptyCircle,
pointSize: 4,
grid: mergeDeep({} as TimeSeriesChartGridSettings,
timeSeriesChartGridDefaultSettings),
@ -154,8 +158,8 @@ export const rangeChartDefaultSettings: RangeChartWidgetSettings = {
xAxis: mergeDeep({} as TimeSeriesChartXAxisSettings,
defaultTimeSeriesChartXAxisSettings,
{showSplitLines: false} as TimeSeriesChartXAxisSettings),
animation: mergeDeep({} as EChartsAnimationSettings,
echartsAnimationDefaultSettings),
animation: mergeDeep({} as ChartAnimationSettings,
chartAnimationDefaultSettings),
thresholds: [],
showLegend: true,
legendPosition: LegendPosition.top,
@ -279,7 +283,7 @@ export const rangeChartTimeSeriesKeySettings = (settings: RangeChartWidgetSettin
pointShape: settings.pointShape,
pointSize: settings.pointSize,
fillAreaSettings: {
type: settings.fillArea ? SeriesFillType.opacity : SeriesFillType.none,
type: settings.fillArea ? ChartFillType.opacity : ChartFillType.none,
opacity: settings.fillAreaOpacity
}
}

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

@ -16,7 +16,7 @@
import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient';
import { Interval, IntervalMath } from '@shared/models/time/time.models';
import { LabelFormatterCallback, SeriesLabelOption } from 'echarts/types/src/util/types';
import { LabelFormatterCallback } from 'echarts/types/src/util/types';
import {
TimeSeriesChartDataItem,
TimeSeriesChartNoAggregationBarWidthStrategy
@ -25,6 +25,7 @@ import { CustomSeriesRenderItemParams } from 'echarts';
import { CallbackDataParams, CustomSeriesRenderItemAPI, CustomSeriesRenderItemReturn } from 'echarts/types/dist/shared';
import { isNumeric } from '@core/utils';
import * as echarts from 'echarts/core';
import { BarSeriesLabelOption } from 'echarts/types/src/chart/bar/BarSeries';
export interface BarVisualSettings {
color: string | LinearGradientObject;
@ -48,7 +49,7 @@ export interface BarRenderContext {
barIndex?: number;
noAggregation?: boolean;
visualSettings?: BarVisualSettings;
labelOption?: SeriesLabelOption;
labelOption?: BarSeriesLabelOption;
additionalLabelOption?: {[key: string]: any};
barStackIndex?: number;
currentStackItems?: TimeSeriesChartDataItem[];

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

@ -18,10 +18,10 @@ import {
TimeSeriesChartStateSettings,
TimeSeriesChartStateSourceType,
TimeSeriesChartTicksFormatter,
TimeSeriesChartTicksGenerator
TimeSeriesChartTicksGenerator,
TimeSeriesChartTooltipValueFormatFunction
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { UtilsService } from '@core/services/utils.service';
import { EChartsTooltipValueFormatFunction } from '@home/components/widget/lib/chart/echarts-widget.models';
import { FormattedData } from '@shared/models/widget.models';
import { formatValue, isDefinedAndNotNull, isNumber, isNumeric } from '@core/utils';
import { LabelFormatterCallback } from 'echarts';
@ -35,7 +35,7 @@ export class TimeSeriesChartStateValueConverter {
public readonly ticksGenerator: TimeSeriesChartTicksGenerator;
public readonly ticksFormatter: TimeSeriesChartTicksFormatter;
public readonly tooltipFormatter: EChartsTooltipValueFormatFunction;
public readonly tooltipFormatter: TimeSeriesChartTooltipValueFormatFunction;
public readonly labelFormatter: LabelFormatterCallback;
public readonly valueConverter: (value: any) => any;

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

@ -27,7 +27,10 @@ import {
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { timeSeriesChartKeyDefaultSettings, TimeSeriesChartKeySettings } from '@home/components/widget/lib/chart/time-series-chart.models';
import {
timeSeriesChartKeyDefaultSettings,
TimeSeriesChartKeySettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { WidgetContext } from '@home/models/widget-component.models';
import { Observable } from 'rxjs';
import { backgroundStyle, ComponentStyle, overlayStyle, textStyle } from '@shared/models/widget-settings.models';

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

@ -14,7 +14,10 @@
/// limitations under the License.
///
import { timeSeriesChartDefaultSettings, TimeSeriesChartSettings } from '@home/components/widget/lib/chart/time-series-chart.models';
import {
timeSeriesChartDefaultSettings,
TimeSeriesChartSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { BackgroundSettings, BackgroundType, Font } from '@shared/models/widget-settings.models';
import { defaultLegendConfig, LegendConfig, LegendPosition, widgetType } from '@shared/models/widget.models';
import { mergeDeep } from '@core/utils';

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

@ -16,27 +16,21 @@
import {
ECharts,
echartsAnimationDefaultSettings,
EChartsAnimationSettings,
EChartsOption,
EChartsSeriesItem,
EChartsShape,
EChartsTooltipTrigger,
EChartsTooltipValueFormatFunction,
EChartsTooltipWidgetSettings,
measureThresholdOffset,
timeAxisBandWidthCalculator
measureThresholdOffset
} from '@home/components/widget/lib/chart/echarts-widget.models';
import {
autoDateFormat,
AutoDateFormatSettings,
ComponentStyle,
DateFormatProcessor,
DateFormatSettings,
Font,
textStyle,
tsToFormatTimeUnit
} from '@shared/models/widget-settings.models';
import {
LabelLayoutOptionCallback,
CallbackDataParams,
TimeAxisBandWidthCalculator,
VisualMapComponentOption,
XAXisOption,
YAXisOption
@ -53,10 +47,9 @@ import {
mergeDeep,
parseFunction
} from '@core/utils';
import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient';
import tinycolor from 'tinycolor2';
import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes';
import { LabelLayoutOption, SeriesLabelOption } from 'echarts/types/src/util/types';
import { TimeAxisBaseOption, ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes';
import { SeriesLabelOption } from 'echarts/types/src/util/types';
import { LabelFormatterCallback } from 'echarts';
import {
BarRenderContext,
@ -64,16 +57,402 @@ import {
BarVisualSettings,
renderTimeSeriesBar
} from '@home/components/widget/lib/chart/time-series-chart-bar.models';
import { DataKey, DataKeySettingsWithComparison, WidgetComparisonSettings } from '@shared/models/widget.models';
import {
DataEntry,
DataKey,
DataKeySettingsWithComparison,
DataSet,
Datasource,
FormattedData,
WidgetComparisonSettings
} from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { TbColorScheme } from '@shared/models/color.models';
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
import { DatePipe } from '@angular/common';
import { BuiltinTextPosition } from 'zrender/src/core/types';
import { CartesianAxisOption } from 'echarts/types/src/coord/cartesian/AxisModel';
import { WidgetTimewindow } from '@shared/models/time/time.models';
import {
calculateAggIntervalWithWidgetTimeWindow,
Interval,
IntervalMath,
WidgetTimewindow
} from '@shared/models/time/time.models';
import { UtilsService } from '@core/services/utils.service';
import { Renderer2 } from '@angular/core';
import {
chartAnimationDefaultSettings,
ChartAnimationSettings,
chartBarDefaultSettings,
ChartBarSettings,
chartColorScheme,
ChartFillSettings,
ChartFillType,
ChartLabelPosition,
ChartShape,
createChartTextStyle,
createLinearOpacityGradient,
PieChartLabelPosition,
prepareChartThemeColor
} from '@home/components/widget/lib/chart/chart.models';
import { BarSeriesLabelOption } from 'echarts/types/src/chart/bar/BarSeries';
type TimeSeriesChartDataEntry = [number, any, number, number];
type TimeSeriesChartDataSet = {name: string; value: TimeSeriesChartDataEntry}[];
export const toTimeSeriesChartDataSet = (data: DataSet, valueConverter?: (value: any) => any): TimeSeriesChartDataSet => {
if (!data?.length) {
return [];
} else {
return data.map(d => {
const ts = isDefinedAndNotNull(d[2]) ? d[2][0] : d[0];
return {
name: ts + '',
value: toTimeSeriesChartDataEntry(d, valueConverter)
};
});
}
};
const toTimeSeriesChartDataEntry = (entry: DataEntry, valueConverter?: (value: any) => any): TimeSeriesChartDataEntry => {
const value = valueConverter ? valueConverter(entry[1]) : entry[1];
const item: TimeSeriesChartDataEntry = [entry[0], value, entry[0], entry[0]];
if (isDefinedAndNotNull(entry[2])) {
item[2] = entry[2][0];
item[3] = entry[2][1];
}
return item;
};
export type TimeSeriesChartTooltipValueFormatFunction =
(value: any, latestData: FormattedData, units?: string, decimals?: number) => string;
export interface TimeSeriesChartDataItem {
id: string;
datasource: Datasource;
dataKey: DataKey;
data: TimeSeriesChartDataSet;
dataSet?: DataSet;
enabled: boolean;
units?: string;
decimals?: number;
latestData?: FormattedData;
tooltipValueFormatFunction?: TimeSeriesChartTooltipValueFormatFunction;
comparisonItem?: boolean;
xAxisIndex: number;
yAxisId: TimeSeriesChartYAxisId;
yAxisIndex: number;
option?: LineSeriesOption | CustomSeriesOption;
barRenderContext?: BarRenderContext;
}
export const timeAxisBandWidthCalculator: TimeAxisBandWidthCalculator = (model) => {
let interval: number;
const axisOption = model.option;
const seriesDataIndices = axisOption.axisPointer?.seriesDataIndices;
if (seriesDataIndices?.length) {
const seriesDataIndex = seriesDataIndices[0];
const series = model.ecModel.getSeriesByIndex(seriesDataIndex.seriesIndex);
if (series) {
const values = series.getData().getValues(seriesDataIndex.dataIndex);
const start = values[2];
const end = values[3];
if (typeof start === 'number' && typeof end === 'number') {
interval = Math.max(end - start, 1);
}
}
}
if (!interval) {
const tbTimeWindow: WidgetTimewindow = (axisOption as any).tbTimeWindow;
if (isDefinedAndNotNull(tbTimeWindow)) {
if (axisOption.axisPointer?.value && typeof axisOption.axisPointer?.value === 'number') {
const intervalArray = calculateAggIntervalWithWidgetTimeWindow(tbTimeWindow, axisOption.axisPointer.value);
const start = intervalArray[0];
const end = intervalArray[1];
interval = Math.max(end - start, 1);
} else {
interval = IntervalMath.numberValue(tbTimeWindow.interval);
}
}
}
if (interval) {
const timeScale = model.axis.scale;
const axisExtent = model.axis.getExtent();
const dataExtent = timeScale.getExtent();
const size = Math.abs(axisExtent[1] - axisExtent[0]);
return interval * (size / (dataExtent[1] - dataExtent[0]));
}
};
const minDataTs = (dataSet: TimeSeriesChartDataSet): number => dataSet.length ? dataSet.map(data =>
Number(data.name)).reduce((a, b) => Math.min(a, b)) : undefined;
const maxDataTs = (dataSet: TimeSeriesChartDataSet): number => dataSet.length ? dataSet.map(data =>
Number(data.name)).reduce((a, b) => Math.max(a, b)) : undefined;
export const adjustTimeAxisExtentToData = (timeAxisOption: TimeAxisBaseOption,
dataItems: TimeSeriesChartDataItem[],
defaultMin: number,
defaultMax: number): void => {
let min: number;
let max: number;
for (const item of dataItems) {
if (item.enabled) {
const minTs = minDataTs(item.data);
if (typeof minTs !== 'undefined') {
min = (typeof min !== 'undefined') ? Math.min(min, minTs) : minTs;
}
const maxTs = maxDataTs(item.data);
if (typeof maxTs !== 'undefined') {
max = (typeof max !== 'undefined') ? Math.max(max, maxTs) : maxTs;
}
}
}
timeAxisOption.min = (typeof min !== 'undefined' && Math.abs(min - defaultMin) < 1000) ? min : defaultMin;
timeAxisOption.max = (typeof max !== 'undefined' && Math.abs(max - defaultMax) < 1000) ? max : defaultMax;
};
export enum TimeSeriesChartTooltipTrigger {
point = 'point',
axis = 'axis'
}
export const tooltipTriggerTranslationMap = new Map<TimeSeriesChartTooltipTrigger, string>(
[
[ TimeSeriesChartTooltipTrigger.point, 'tooltip.trigger-point' ],
[ TimeSeriesChartTooltipTrigger.axis, 'tooltip.trigger-axis' ]
]
);
export interface TimeSeriesChartTooltipWidgetSettings {
showTooltip: boolean;
tooltipTrigger?: TimeSeriesChartTooltipTrigger;
tooltipShowFocusedSeries?: boolean;
tooltipLabelFont: Font;
tooltipLabelColor: string;
tooltipValueFont: Font;
tooltipValueColor: string;
tooltipValueFormatter?: string | TimeSeriesChartTooltipValueFormatFunction;
tooltipShowDate: boolean;
tooltipDateInterval?: boolean;
tooltipDateFormat: DateFormatSettings;
tooltipDateFont: Font;
tooltipDateColor: string;
tooltipBackgroundColor: string;
tooltipBackgroundBlur: number;
}
export const createTooltipValueFormatFunction =
(tooltipValueFormatter: string | TimeSeriesChartTooltipValueFormatFunction): TimeSeriesChartTooltipValueFormatFunction => {
let tooltipValueFormatFunction: TimeSeriesChartTooltipValueFormatFunction;
if (isFunction(tooltipValueFormatter)) {
tooltipValueFormatFunction = tooltipValueFormatter as TimeSeriesChartTooltipValueFormatFunction;
} else if (typeof tooltipValueFormatter === 'string' && tooltipValueFormatter.length) {
try {
tooltipValueFormatFunction =
new Function('value', 'latestData', tooltipValueFormatter) as TimeSeriesChartTooltipValueFormatFunction;
} catch (e) {}
}
return tooltipValueFormatFunction;
};
export const timeSeriesChartTooltipFormatter = (renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: TimeSeriesChartTooltipWidgetSettings,
params: CallbackDataParams[] | CallbackDataParams,
valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction,
focusedSeriesIndex: number,
series?: TimeSeriesChartDataItem[],
interval?: Interval): null | HTMLElement => {
const tooltipParams = mapTooltipParams(params, series, focusedSeriesIndex);
if (!tooltipParams.items.length && !tooltipParams.comparisonItems.length) {
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', '16px');
buildItemsTooltip(tooltipElement, tooltipParams.items, renderer, tooltipDateFormat, settings, valueFormatFunction, interval);
buildItemsTooltip(tooltipElement, tooltipParams.comparisonItems, renderer, tooltipDateFormat, settings, valueFormatFunction, interval);
return tooltipElement;
};
interface TooltipItem {
param: CallbackDataParams;
dataItem: TimeSeriesChartDataItem;
}
interface TooltipParams {
items: TooltipItem[];
comparisonItems: TooltipItem[];
}
const buildItemsTooltip = (tooltipElement: HTMLElement,
items: TooltipItem[],
renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: TimeSeriesChartTooltipWidgetSettings,
valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction,
interval?: Interval) => {
if (items.length) {
const tooltipItemsElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(tooltipItemsElement, 'display', 'flex');
renderer.setStyle(tooltipItemsElement, 'flex-direction', 'column');
renderer.setStyle(tooltipItemsElement, 'align-items', 'flex-start');
renderer.setStyle(tooltipItemsElement, 'gap', '4px');
renderer.appendChild(tooltipElement, tooltipItemsElement);
if (settings.tooltipShowDate) {
renderer.appendChild(tooltipItemsElement,
constructTooltipDateElement(renderer, tooltipDateFormat, settings, items[0].param, interval));
}
for (const item of items) {
renderer.appendChild(tooltipItemsElement,
constructTooltipSeriesElement(renderer, settings, item, valueFormatFunction));
}
}
};
const mapTooltipParams = (params: CallbackDataParams[] | CallbackDataParams,
series?: TimeSeriesChartDataItem[],
focusedSeriesIndex?: number): TooltipParams => {
const result: TooltipParams = {
items: [],
comparisonItems: []
};
if (!params || Array.isArray(params) && !params[0]) {
return result;
}
const firstParam = Array.isArray(params) ? params[0] : params;
if (!firstParam.value) {
return result;
}
let seriesParams: CallbackDataParams = null;
if (Array.isArray(params) && focusedSeriesIndex > -1) {
seriesParams = params.find(param => param.seriesIndex === focusedSeriesIndex);
} else if (!Array.isArray(params)) {
seriesParams = params;
}
if (seriesParams) {
appendTooltipItem(result, seriesParams, series);
} else if (Array.isArray(params)) {
for (seriesParams of params) {
appendTooltipItem(result, seriesParams, series);
}
}
return result;
};
const appendTooltipItem = (tooltipParams: TooltipParams, seriesParams: CallbackDataParams, series?: TimeSeriesChartDataItem[]) => {
const dataItem = series?.find(s => s.id === seriesParams.seriesId);
const tooltipItem: TooltipItem = {
param: seriesParams,
dataItem
};
if (dataItem?.comparisonItem) {
tooltipParams.comparisonItems.push(tooltipItem);
} else {
tooltipParams.items.push(tooltipItem);
}
};
const constructTooltipDateElement = (renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: TimeSeriesChartTooltipWidgetSettings,
param: CallbackDataParams,
interval?: Interval): HTMLElement => {
const dateElement: HTMLElement = renderer.createElement('div');
let dateText: string;
const startTs = param.value[2];
const endTs = param.value[3];
if (settings.tooltipDateInterval && startTs && endTs && (endTs - 1) > startTs) {
const startDateText = tooltipDateFormat.update(startTs, interval);
const endDateText = tooltipDateFormat.update(endTs - 1, interval);
if (startDateText === endDateText) {
dateText = startDateText;
} else {
dateText = startDateText + ' - ' + endDateText;
}
} else {
const ts = param.value[0];
dateText = tooltipDateFormat.update(ts, interval);
}
renderer.appendChild(dateElement, renderer.createText(dateText));
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);
return dateElement;
};
const constructTooltipSeriesElement = (renderer: Renderer2,
settings: TimeSeriesChartTooltipWidgetSettings,
item: TooltipItem,
valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction): 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', item.param.color);
renderer.appendChild(labelElement, circleElement);
const labelTextElement: HTMLElement = renderer.createElement('div');
renderer.appendChild(labelTextElement, renderer.createText(item.param.seriesName));
renderer.setStyle(labelTextElement, 'font-family', settings.tooltipLabelFont.family);
renderer.setStyle(labelTextElement, 'font-size', settings.tooltipLabelFont.size + settings.tooltipLabelFont.sizeUnit);
renderer.setStyle(labelTextElement, 'font-style', settings.tooltipLabelFont.style);
renderer.setStyle(labelTextElement, 'font-weight', settings.tooltipLabelFont.weight);
renderer.setStyle(labelTextElement, 'line-height', settings.tooltipLabelFont.lineHeight);
renderer.setStyle(labelTextElement, 'color', settings.tooltipLabelColor);
renderer.appendChild(labelElement, labelTextElement);
const valueElement: HTMLElement = renderer.createElement('div');
let formatFunction = valueFormatFunction;
let latestData: FormattedData;
let units = '';
let decimals = 0;
if (item.dataItem) {
if (item.dataItem.tooltipValueFormatFunction) {
formatFunction = item.dataItem.tooltipValueFormatFunction;
}
latestData = item.dataItem.latestData;
units = item.dataItem.units;
decimals = item.dataItem.decimals;
}
if (!latestData) {
latestData = {} as FormattedData;
}
const value = formatFunction(item.param.value[1], latestData, units, decimals);
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;
};
export enum TimeSeriesChartType {
default = 'default',
@ -91,41 +470,6 @@ export const timeSeriesChartTypeTranslations = new Map<TimeSeriesChartType, stri
]
);
export const timeSeriesChartColorScheme: TbColorScheme = {
'threshold.line': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
},
'threshold.label': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
},
'axis.line': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.label': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.ticks': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.tickLabel': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.splitLine': {
light: 'rgba(0, 0, 0, 0.12)',
dark: '#484753'
},
'series.label': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
}
};
export enum AxisPosition {
left = 'left',
right = 'right',
@ -225,36 +569,6 @@ export const timeSeriesStateSourceTypeTranslations = new Map<TimeSeriesChartStat
]
);
export enum SeriesFillType {
none = 'none',
opacity = 'opacity',
gradient = 'gradient'
}
export const seriesFillTypes = Object.keys(SeriesFillType) as SeriesFillType[];
export const seriesFillTypeTranslations = new Map<SeriesFillType, string>(
[
[SeriesFillType.none, 'widgets.time-series-chart.series.fill-type-none'],
[SeriesFillType.opacity, 'widgets.time-series-chart.series.fill-type-opacity'],
[SeriesFillType.gradient, 'widgets.time-series-chart.series.fill-type-gradient']
]
);
export enum SeriesLabelPosition {
top = 'top',
bottom = 'bottom'
}
export const seriesLabelPositions = Object.keys(SeriesLabelPosition) as SeriesLabelPosition[];
export const seriesLabelPositionTranslations = new Map<SeriesLabelPosition, string>(
[
[SeriesLabelPosition.top, 'widgets.time-series-chart.series.label-position-top'],
[SeriesLabelPosition.bottom, 'widgets.time-series-chart.series.label-position-bottom']
]
);
export enum LineSeriesStepType {
start = 'start',
middle = 'middle',
@ -384,25 +698,25 @@ export const defaultTimeSeriesChartYAxisSettings: TimeSeriesChartYAxisSettings =
weight: '600',
lineHeight: '1'
},
labelColor: timeSeriesChartColorScheme['axis.label'].light,
position: AxisPosition.left,
showTickLabels: true,
tickLabelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
labelColor: chartColorScheme['axis.label'].light,
position: AxisPosition.left,
showTickLabels: true,
tickLabelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light,
ticksFormatter: null,
showTicks: true,
ticksColor: timeSeriesChartColorScheme['axis.ticks'].light,
showLine: true,
lineColor: timeSeriesChartColorScheme['axis.line'].light,
showSplitLines: true,
splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light
tickLabelColor: chartColorScheme['axis.tickLabel'].light,
ticksFormatter: null,
showTicks: true,
ticksColor: chartColorScheme['axis.ticks'].light,
showLine: true,
lineColor: chartColorScheme['axis.line'].light,
showSplitLines: true,
splitLinesColor: chartColorScheme['axis.splitLine'].light
};
export const defaultTimeSeriesChartXAxisSettings: TimeSeriesChartXAxisSettings = {
@ -416,7 +730,7 @@ export const defaultTimeSeriesChartXAxisSettings: TimeSeriesChartXAxisSettings =
weight: '600',
lineHeight: '1'
},
labelColor: timeSeriesChartColorScheme['axis.label'].light,
labelColor: chartColorScheme['axis.label'].light,
position: AxisPosition.bottom,
showTickLabels: true,
tickLabelFont: {
@ -427,14 +741,14 @@ export const defaultTimeSeriesChartXAxisSettings: TimeSeriesChartXAxisSettings =
weight: '400',
lineHeight: '1'
},
tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light,
tickLabelColor: chartColorScheme['axis.tickLabel'].light,
ticksFormat: {},
showTicks: true,
ticksColor: timeSeriesChartColorScheme['axis.ticks'].light,
ticksColor: chartColorScheme['axis.ticks'].light,
showLine: true,
lineColor: timeSeriesChartColorScheme['axis.line'].light,
lineColor: chartColorScheme['axis.line'].light,
showSplitLines: true,
splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light
splitLinesColor: chartColorScheme['axis.splitLine'].light
};
export type TimeSeriesChartYAxes = {[id: TimeSeriesChartYAxisId]: TimeSeriesChartYAxisSettings};
@ -453,9 +767,9 @@ export interface TimeSeriesChartThreshold {
lineColor: string;
lineType: TimeSeriesChartLineType | number | number[];
lineWidth: number;
startSymbol: EChartsShape;
startSymbol: ChartShape;
startSymbolSize: number;
endSymbol: EChartsShape;
endSymbol: ChartShape;
endSymbolSize: number;
showLabel: boolean;
labelPosition: ThresholdLabelPosition;
@ -505,12 +819,12 @@ export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold =
yAxisId: 'default',
units: null,
decimals: 0,
lineColor: timeSeriesChartColorScheme['threshold.line'].light,
lineColor: chartColorScheme['threshold.line'].light,
lineType: TimeSeriesChartLineType.solid,
lineWidth: 1,
startSymbol: EChartsShape.none,
startSymbol: ChartShape.none,
startSymbolSize: 5,
endSymbol: EChartsShape.arrow,
endSymbol: ChartShape.arrow,
endSymbolSize: 5,
showLabel: true,
labelPosition: ThresholdLabelPosition.end,
@ -522,7 +836,7 @@ export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold =
weight: '400',
lineHeight: '1'
},
labelColor: timeSeriesChartColorScheme['threshold.label'].light,
labelColor: chartColorScheme['threshold.label'].light,
enableLabelBackground: false,
labelBackground: 'rgba(255,255,255,0.56)'
};
@ -657,7 +971,7 @@ export const timeSeriesChartGridDefaultSettings: TimeSeriesChartGridSettings = {
borderColor: '#ccc'
};
export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings, TimeSeriesChartComparisonSettings {
export interface TimeSeriesChartSettings extends TimeSeriesChartTooltipWidgetSettings, TimeSeriesChartComparisonSettings {
thresholds: TimeSeriesChartThreshold[];
darkMode: boolean;
dataZoom: boolean;
@ -665,7 +979,7 @@ export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings, T
grid: TimeSeriesChartGridSettings;
yAxes: TimeSeriesChartYAxes;
xAxis: TimeSeriesChartXAxisSettings;
animation: EChartsAnimationSettings;
animation: ChartAnimationSettings;
barWidthSettings: TimeSeriesChartBarWidthSettings;
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
visualMapSettings?: TimeSeriesChartVisualMapSettings;
@ -686,8 +1000,8 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
},
xAxis: mergeDeep({} as TimeSeriesChartXAxisSettings,
defaultTimeSeriesChartXAxisSettings),
animation: mergeDeep({} as EChartsAnimationSettings,
echartsAnimationDefaultSettings),
animation: mergeDeep({} as ChartAnimationSettings,
chartAnimationDefaultSettings),
barWidthSettings: {
barGap: 0.3,
intervalGap: 0.6
@ -695,7 +1009,7 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
noAggregationBarWidthSettings: mergeDeep({} as TimeSeriesChartNoAggregationBarWidthSettings,
timeSeriesChartNoAggregationBarWidthDefaultSettings),
showTooltip: true,
tooltipTrigger: EChartsTooltipTrigger.axis,
tooltipTrigger: TimeSeriesChartTooltipTrigger.axis,
tooltipLabelFont: {
family: 'Roboto',
size: 12,
@ -736,15 +1050,6 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
{ position: AxisPosition.top } as TimeSeriesChartXAxisSettings)
};
export interface SeriesFillSettings {
type: SeriesFillType;
opacity: number;
gradient: {
start: number;
end: number;
};
}
export interface LineSeriesSettings {
showLine: boolean;
step: boolean;
@ -754,31 +1059,15 @@ export interface LineSeriesSettings {
lineWidth: number;
showPoints: boolean;
showPointLabel: boolean;
pointLabelPosition: SeriesLabelPosition;
pointLabelPosition: ChartLabelPosition;
pointLabelFont: Font;
pointLabelColor: string;
enablePointLabelBackground: boolean;
pointLabelBackground: string;
pointLabelFormatter?: string | LabelFormatterCallback;
pointShape: EChartsShape;
pointShape: ChartShape;
pointSize: number;
fillAreaSettings: SeriesFillSettings;
}
export interface BarSeriesSettings {
showBorder: boolean;
borderWidth: number;
borderRadius: number;
showLabel: boolean;
labelPosition: SeriesLabelPosition | BuiltinTextPosition;
labelFont: Font;
labelColor: string;
enableLabelBackground: boolean;
labelBackground: string;
labelFormatter?: string | LabelFormatterCallback;
labelLayout?: LabelLayoutOption | LabelLayoutOptionCallback;
additionalLabelOption?: {[key: string]: any};
backgroundSettings: SeriesFillSettings;
fillAreaSettings: ChartFillSettings;
}
export interface TimeSeriesChartKeySettings extends DataKeySettingsWithComparison {
@ -787,8 +1076,8 @@ export interface TimeSeriesChartKeySettings extends DataKeySettingsWithCompariso
dataHiddenByDefault: boolean;
type: TimeSeriesChartSeriesType;
lineSettings: LineSeriesSettings;
barSettings: BarSeriesSettings;
tooltipValueFormatter?: string | EChartsTooltipValueFormatFunction;
barSettings: ChartBarSettings;
tooltipValueFormatter?: string | TimeSeriesChartTooltipValueFormatFunction;
}
export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
@ -805,7 +1094,7 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
lineWidth: 2,
showPoints: false,
showPointLabel: false,
pointLabelPosition: SeriesLabelPosition.top,
pointLabelPosition: ChartLabelPosition.top,
pointLabelFont: {
family: 'Roboto',
size: 11,
@ -814,39 +1103,13 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
weight: '400',
lineHeight: '1'
},
pointLabelColor: timeSeriesChartColorScheme['series.label'].light,
pointLabelColor: chartColorScheme['series.label'].light,
enablePointLabelBackground: false,
pointLabelBackground: 'rgba(255,255,255,0.56)',
pointShape: EChartsShape.emptyCircle,
pointShape: ChartShape.emptyCircle,
pointSize: 4,
fillAreaSettings: {
type: SeriesFillType.none,
opacity: 0.4,
gradient: {
start: 100,
end: 0
}
}
},
barSettings: {
showBorder: false,
borderWidth: 2,
borderRadius: 0,
showLabel: false,
labelPosition: SeriesLabelPosition.top,
labelFont: {
family: 'Roboto',
size: 11,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
labelColor: timeSeriesChartColorScheme['series.label'].light,
enableLabelBackground: false,
labelBackground: 'rgba(255,255,255,0.56)',
backgroundSettings: {
type: SeriesFillType.none,
type: ChartFillType.none,
opacity: 0.4,
gradient: {
start: 100,
@ -854,6 +1117,7 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
}
}
},
barSettings: mergeDeep({} as ChartBarSettings, chartBarDefaultSettings),
comparisonSettings: {
showValuesForComparison: false,
comparisonValuesLabel: '',
@ -861,14 +1125,6 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
}
};
export interface TimeSeriesChartDataItem extends EChartsSeriesItem {
xAxisIndex: number;
yAxisId: TimeSeriesChartYAxisId;
yAxisIndex: number;
option?: LineSeriesOption | CustomSeriesOption;
barRenderContext?: BarRenderContext;
}
type TimeSeriesChartThresholdValue = number | string | (number | string)[];
export interface TimeSeriesChartThresholdItem {
@ -1288,7 +1544,7 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[],
return series;
};
export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings,
export const updateDarkMode = (options: EChartsOption,
xAxisList: TimeSeriesChartXAxis[],
yAxisList: TimeSeriesChartYAxis[],
dataItems: TimeSeriesChartDataItem[],
@ -1332,7 +1588,7 @@ export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChart
}
} else {
if (item.barRenderContext?.labelOption?.show) {
const barSettings = item.dataKey.settings as BarSeriesSettings;
const barSettings = item.dataKey.settings as ChartBarSettings;
(item.barRenderContext.labelOption.rich.value as any).fill = prepareChartThemeColor(barSettings.labelColor,
darkMode, 'series.label');
}
@ -1381,7 +1637,7 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
lineSettings.pointLabelFont, lineSettings.pointLabelColor,
lineSettings.enablePointLabelBackground, lineSettings.pointLabelBackground,
lineSettings.pointLabelPosition,
lineSettings.pointLabelFormatter, false, darkMode);
lineSettings.pointLabelFormatter, false, darkMode) as SeriesLabelOption;
lineSeriesOption.step = lineSettings.step ? lineSettings.stepType : false;
lineSeriesOption.smooth = lineSettings.smooth ? 0.25 : false;
if (lineSettings.smooth) {
@ -1391,11 +1647,11 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
width: lineSettings.showLine ? lineSettings.lineWidth : 0,
type: lineSettings.lineType
};
if (lineSettings.fillAreaSettings.type !== SeriesFillType.none) {
if (lineSettings.fillAreaSettings.type !== ChartFillType.none) {
lineSeriesOption.areaStyle = {};
if (lineSettings.fillAreaSettings.type === SeriesFillType.opacity) {
if (lineSettings.fillAreaSettings.type === ChartFillType.opacity) {
lineSeriesOption.areaStyle.opacity = lineSettings.fillAreaSettings.opacity;
} else if (lineSettings.fillAreaSettings.type === SeriesFillType.gradient) {
} else if (lineSettings.fillAreaSettings.type === ChartFillType.gradient) {
lineSeriesOption.areaStyle.opacity = 1;
lineSeriesOption.areaStyle.color = createLinearOpacityGradient(seriesColor, lineSettings.fillAreaSettings.gradient);
}
@ -1413,9 +1669,9 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
borderWidth: barSettings.showBorder ? barSettings.borderWidth : 0,
borderRadius: barSettings.borderRadius
};
if (barSettings.backgroundSettings.type === SeriesFillType.none) {
if (barSettings.backgroundSettings.type === ChartFillType.none) {
barVisualSettings.color = seriesColor;
} else if (barSettings.backgroundSettings.type === SeriesFillType.opacity) {
} else if (barSettings.backgroundSettings.type === ChartFillType.opacity) {
barVisualSettings.color = tinycolor(seriesColor).setAlpha(barSettings.backgroundSettings.opacity).toRgbString();
} else {
barVisualSettings.color = createLinearOpacityGradient(seriesColor, barSettings.backgroundSettings.gradient);
@ -1437,10 +1693,10 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
const createSeriesLabelOption = (item: TimeSeriesChartDataItem, show: boolean,
labelFont: Font, labelColor: string,
enableBackground: boolean, labelBackground: string,
position: SeriesLabelPosition | BuiltinTextPosition,
position: ChartLabelPosition | PieChartLabelPosition | BuiltinTextPosition,
labelFormatter: string | LabelFormatterCallback,
labelColorFill: boolean,
darkMode: boolean): SeriesLabelOption => {
darkMode: boolean): SeriesLabelOption | BarSeriesLabelOption => {
let labelStyle: ComponentStyle = {};
if (show) {
labelStyle = createChartTextStyle(labelFont, labelColor, darkMode, 'series.label', labelColorFill);
@ -1467,7 +1723,7 @@ const createSeriesLabelOption = (item: TimeSeriesChartDataItem, show: boolean,
}
return result;
};
const labelOption: SeriesLabelOption = {
const labelOption: SeriesLabelOption | BarSeriesLabelOption = {
show,
position,
formatter,
@ -1482,45 +1738,3 @@ const createSeriesLabelOption = (item: TimeSeriesChartDataItem, show: boolean,
}
return labelOption;
};
const createChartTextStyle = (font: Font, color: string, darkMode: boolean, colorKey?: string, fill = false): ComponentStyle => {
const style = textStyle(font);
delete style.lineHeight;
style.fontSize = font.size;
if (fill) {
style.fill = prepareChartThemeColor(color, darkMode, colorKey);
} else {
style.color = prepareChartThemeColor(color, darkMode, colorKey);
}
return style;
};
const createLinearOpacityGradient = (color: string, gradient: {start: number; end: number}): LinearGradientObject => ({
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: tinycolor(color).setAlpha(gradient.start / 100).toRgbString() // color at 0%
}, {
offset: 1, color: tinycolor(color).setAlpha(gradient.end / 100).toRgbString() // color at 100%
}],
global: false
});
const prepareChartThemeColor = (color: string, darkMode: boolean, colorKey?: string): string => {
if (darkMode) {
let colorInstance = tinycolor(color);
if (colorInstance.isDark()) {
if (colorKey && timeSeriesChartColorScheme[colorKey]) {
return timeSeriesChartColorScheme[colorKey].dark;
} else {
const rgb = colorInstance.toRgb();
colorInstance = tinycolor({r: 255 - rgb.r, g: 255 - rgb.g, b: 255 - rgb.b, a: rgb.a});
return colorInstance.toRgbString();
}
}
}
return color;
};

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

@ -16,15 +16,16 @@
import { WidgetContext } from '@home/models/widget-component.models';
import {
adjustTimeAxisExtentToData,
calculateThresholdsOffset,
createTimeSeriesVisualMapOption,
createTimeSeriesXAxis,
createTimeSeriesYAxis,
createTooltipValueFormatFunction,
defaultTimeSeriesChartYAxisSettings,
generateChartData,
LineSeriesStepType,
parseThresholdData,
SeriesLabelPosition,
TimeSeriesChartAxis,
TimeSeriesChartDataItem,
timeSeriesChartDefaultSettings,
@ -37,30 +38,27 @@ import {
timeSeriesChartThresholdDefaultSettings,
TimeSeriesChartThresholdItem,
TimeSeriesChartThresholdType,
timeSeriesChartTooltipFormatter,
TimeSeriesChartTooltipTrigger,
TimeSeriesChartTooltipValueFormatFunction,
TimeSeriesChartType,
TimeSeriesChartXAxis,
TimeSeriesChartYAxis,
TimeSeriesChartYAxisId,
TimeSeriesChartYAxisSettings,
toTimeSeriesChartDataSet,
updateDarkMode,
updateXAxisTimeWindow
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { ResizeObserver } from '@juggle/resize-observer';
import {
adjustTimeAxisExtentToData,
calculateAxisSize,
createTooltipValueFormatFunction,
ECharts,
echartsModule,
EChartsOption,
EChartsShape,
echartsTooltipFormatter,
EChartsTooltipTrigger,
EChartsTooltipValueFormatFunction,
getAxisExtent,
getFocusedSeriesIndex,
measureAxisNameSize,
toNamedData
measureAxisNameSize
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { DateFormatProcessor } from '@shared/models/widget-settings.models';
import { formattedDataFormDatasourceData, formatValue, isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils';
@ -76,6 +74,7 @@ import { DataKeySettingsFunction } from '@home/components/widget/config/data-key
import { DeepPartial } from '@shared/models/common';
import { BarRenderSharedContext } from '@home/components/widget/lib/chart/time-series-chart-bar.models';
import { TimeSeriesChartStateValueConverter } from '@home/components/widget/lib/chart/time-series-chart-state.models';
import { ChartLabelPosition, ChartShape } from '@home/components/widget/lib/chart/chart.models';
export class TbTimeSeriesChart {
@ -92,14 +91,14 @@ export class TbTimeSeriesChart {
settings.type = TimeSeriesChartSeriesType.line;
settings.lineSettings.showLine = false;
settings.lineSettings.showPoints = true;
settings.lineSettings.pointShape = EChartsShape.circle;
settings.lineSettings.pointShape = ChartShape.circle;
settings.lineSettings.pointSize = 8;
} else if (type === TimeSeriesChartType.state) {
settings.type = TimeSeriesChartSeriesType.line;
settings.lineSettings.showLine = true;
settings.lineSettings.step = true;
settings.lineSettings.stepType = LineSeriesStepType.end;
settings.lineSettings.pointShape = EChartsShape.circle;
settings.lineSettings.pointShape = ChartShape.circle;
settings.lineSettings.pointSize = 12;
}
return settings;
@ -135,7 +134,7 @@ export class TbTimeSeriesChart {
private timeSeriesChartOptions: EChartsOption;
private readonly tooltipDateFormat: DateFormatProcessor;
private readonly tooltipValueFormatFunction: EChartsTooltipValueFormatFunction;
private readonly tooltipValueFormatFunction: TimeSeriesChartTooltipValueFormatFunction;
private readonly stateValueConverter: TimeSeriesChartStateValueConverter;
private yMinSubject = new BehaviorSubject(-1);
@ -189,7 +188,7 @@ export class TbTimeSeriesChart {
this.tooltipValueFormatFunction =
createTooltipValueFormatFunction(this.settings.tooltipValueFormatter);
if (!this.tooltipValueFormatFunction) {
this.tooltipValueFormatFunction = (value, latestData, units, decimals) => formatValue(value, decimals, units, false);
this.tooltipValueFormatFunction = (value, _latestData, units, decimals) => formatValue(value, decimals, units, false);
}
}
}
@ -218,7 +217,7 @@ export class TbTimeSeriesChart {
const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === item.dataKey) : null;
if (!isEqual(item.dataSet, datasourceData?.data)) {
item.dataSet = datasourceData?.data;
item.data = datasourceData?.data ? toNamedData(datasourceData.data, this.stateValueConverter?.valueConverter) : [];
item.data = datasourceData?.data ? toTimeSeriesChartDataSet(datasourceData.data, this.stateValueConverter?.valueConverter) : [];
}
}
this.onResize();
@ -352,7 +351,7 @@ export class TbTimeSeriesChart {
this.darkMode = darkMode;
if (this.timeSeriesChart) {
this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions,
this.settings, this.xAxisList, this.yAxisList, this.dataItems, darkMode);
this.xAxisList, this.yAxisList, this.dataItems, darkMode);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions);
}
}
@ -381,11 +380,11 @@ export class TbTimeSeriesChart {
const keySettings = mergeDeep<TimeSeriesChartKeySettings>({} as TimeSeriesChartKeySettings,
timeSeriesChartKeyDefaultSettings, dataKey.settings);
if ((keySettings.type === TimeSeriesChartSeriesType.line && keySettings.lineSettings.showPointLabel &&
keySettings.lineSettings.pointLabelPosition === SeriesLabelPosition.top) ||
keySettings.lineSettings.pointLabelPosition === ChartLabelPosition.top) ||
(keySettings.type === TimeSeriesChartSeriesType.bar &&
keySettings.barSettings.showLabel &&
[SeriesLabelPosition.top, SeriesLabelPosition.bottom]
.includes(keySettings.barSettings.labelPosition as SeriesLabelPosition))) {
[ChartLabelPosition.top, ChartLabelPosition.bottom]
.includes(keySettings.barSettings.labelPosition as ChartLabelPosition))) {
this.topPointLabels = true;
}
if (this.stateValueConverter && keySettings.type === TimeSeriesChartSeriesType.line) {
@ -393,7 +392,8 @@ export class TbTimeSeriesChart {
}
dataKey.settings = keySettings;
const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === dataKey) : null;
const namedData = datasourceData?.data ? toNamedData(datasourceData.data, this.stateValueConverter?.valueConverter) : [];
const data = datasourceData?.data ?
toTimeSeriesChartDataSet(datasourceData.data, this.stateValueConverter?.valueConverter) : [];
const units = dataKey.units && dataKey.units.length ? dataKey.units : this.ctx.units;
const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals :
(isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2);
@ -413,7 +413,7 @@ export class TbTimeSeriesChart {
comparisonItem,
datasource,
dataKey,
data: namedData,
data,
enabled: !keySettings.dataHiddenByDefault,
tooltipValueFormatFunction: createTooltipValueFormatFunction(keySettings.tooltipValueFormatter)
});
@ -596,14 +596,14 @@ export class TbTimeSeriesChart {
darkMode: this.darkMode,
backgroundColor: 'transparent',
tooltip: [{
trigger: this.settings.tooltipTrigger === EChartsTooltipTrigger.axis ? 'axis' : 'item',
trigger: this.settings.tooltipTrigger === TimeSeriesChartTooltipTrigger.axis ? 'axis' : 'item',
confine: true,
appendTo: 'body',
axisPointer: {
type: this.noAggregation ? 'line' : 'shadow'
},
formatter: (params: CallbackDataParams[]) =>
this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat,
this.settings.showTooltip ? timeSeriesChartTooltipFormatter(this.renderer, this.tooltipDateFormat,
this.settings, params, this.tooltipValueFormatFunction,
this.settings.tooltipShowFocusedSeries ? getFocusedSeriesIndex(this.timeSeriesChart) : -1,
this.dataItems, this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined,

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

@ -0,0 +1,169 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ng-container [formGroup]="barChartWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-chart-style</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="sortSeries">
{{ 'widgets.latest-chart.sort-series' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-appearance</div>
<tb-chart-bar-settings
formControlName="barSettings"
[series]="false">
</tb-chart-bar-settings>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-axis</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.chart.chart-axis.scale</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.chart.chart-axis.scale-min</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisMin"
type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.chart.chart-axis.scale-max</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisMax"
type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="axisTickLabelFont"
clearButton
disabledLineHeight
forceSizeUnit="px"
previewText="100">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="axisTickLabelColor">
</tb-color-input>
</div>
</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="Wind power">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'legend.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendValueFont"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendValueColor">
</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 column-xs">
<div>{{ 'tooltip.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="tooltipValueType">
<mat-option *ngFor="let type of latestChartTooltipValueTypes" [value]="type">
{{ latestChartTooltipValueTypeTranslationMap.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipValueDecimals" 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="tooltipValueFont"
[previewText]="tooltipValuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipValueColor">
</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>
<tb-chart-animation-settings
formControlName="animation">
</tb-chart-animation-settings>
<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>

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

@ -0,0 +1,160 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component } from '@angular/core';
import {
legendPositions,
legendPositionTranslationMap,
WidgetSettings,
WidgetSettingsComponent
} from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { formatValue, mergeDeep } from '@core/utils';
import {
LatestChartTooltipValueType,
latestChartTooltipValueTypes,
latestChartTooltipValueTypeTranslations
} from '@home/components/widget/lib/chart/latest-chart.models';
import {
barChartWidgetDefaultSettings,
BarChartWidgetSettings
} from '@home/components/widget/lib/chart/bar-chart-widget.models';
@Component({
selector: 'tb-bar-chart-widget-settings',
templateUrl: './bar-chart-widget-settings.component.html',
styleUrls: []
})
export class BarChartWidgetSettingsComponent extends WidgetSettingsComponent {
legendPositions = legendPositions;
legendPositionTranslationMap = legendPositionTranslationMap;
latestChartTooltipValueTypes = latestChartTooltipValueTypes;
latestChartTooltipValueTypeTranslationMap = latestChartTooltipValueTypeTranslations;
barChartWidgetSettingsForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.barChartWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return mergeDeep<BarChartWidgetSettings>({} as BarChartWidgetSettings, barChartWidgetDefaultSettings);
}
protected onSettingsSet(settings: WidgetSettings) {
this.barChartWidgetSettingsForm = this.fb.group({
sortSeries: [settings.sortSeries, []],
barSettings: [settings.barSettings, []],
axisMin: [settings.axisMin, []],
axisMax: [settings.axisMax, []],
axisTickLabelFont: [settings.axisTickLabelFont, []],
axisTickLabelColor: [settings.axisTickLabelColor, []],
animation: [settings.animation, []],
showLegend: [settings.showLegend, []],
legendPosition: [settings.legendPosition, []],
legendLabelFont: [settings.legendLabelFont, []],
legendLabelColor: [settings.legendLabelColor, []],
legendValueFont: [settings.legendValueFont, []],
legendValueColor: [settings.legendValueColor, []],
showTooltip: [settings.showTooltip, []],
tooltipValueType: [settings.tooltipValueType, []],
tooltipValueDecimals: [settings.tooltipValueDecimals, []],
tooltipValueFont: [settings.tooltipValueFont, []],
tooltipValueColor: [settings.tooltipValueColor, []],
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []],
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
background: [settings.background, []]
});
}
protected validatorTriggers(): string[] {
return ['showLegend', 'showTooltip'];
}
protected updateValidators(emitEvent: boolean) {
const showLegend: boolean = this.barChartWidgetSettingsForm.get('showLegend').value;
const showTooltip: boolean = this.barChartWidgetSettingsForm.get('showTooltip').value;
if (showLegend) {
this.barChartWidgetSettingsForm.get('legendPosition').enable();
this.barChartWidgetSettingsForm.get('legendLabelFont').enable();
this.barChartWidgetSettingsForm.get('legendLabelColor').enable();
this.barChartWidgetSettingsForm.get('legendValueFont').enable();
this.barChartWidgetSettingsForm.get('legendValueColor').enable();
} else {
this.barChartWidgetSettingsForm.get('legendPosition').disable();
this.barChartWidgetSettingsForm.get('legendLabelFont').disable();
this.barChartWidgetSettingsForm.get('legendLabelColor').disable();
this.barChartWidgetSettingsForm.get('legendValueFont').disable();
this.barChartWidgetSettingsForm.get('legendValueColor').disable();
}
if (showTooltip) {
this.barChartWidgetSettingsForm.get('tooltipValueType').enable();
this.barChartWidgetSettingsForm.get('tooltipValueDecimals').enable();
this.barChartWidgetSettingsForm.get('tooltipValueFont').enable();
this.barChartWidgetSettingsForm.get('tooltipValueColor').enable();
this.barChartWidgetSettingsForm.get('tooltipBackgroundColor').enable();
this.barChartWidgetSettingsForm.get('tooltipBackgroundBlur').enable();
} else {
this.barChartWidgetSettingsForm.get('tooltipValueType').disable();
this.barChartWidgetSettingsForm.get('tooltipValueDecimals').disable();
this.barChartWidgetSettingsForm.get('tooltipValueFont').disable();
this.barChartWidgetSettingsForm.get('tooltipValueColor').disable();
this.barChartWidgetSettingsForm.get('tooltipBackgroundColor').disable();
this.barChartWidgetSettingsForm.get('tooltipBackgroundBlur').disable();
}
}
private _valuePreviewFn(): string {
const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals;
return formatValue(110, decimals, units, false);
}
private _tooltipValuePreviewFn(): string {
const tooltipValueType: LatestChartTooltipValueType = this.barChartWidgetSettingsForm.get('tooltipValueType').value;
const decimals: number = this.barChartWidgetSettingsForm.get('tooltipValueDecimals').value;
if (tooltipValueType === LatestChartTooltipValueType.percentage) {
return formatValue(35, decimals, '%', false);
} else {
const units: string = this.widgetConfig.config.units;
return formatValue(110, decimals, units, false);
}
}
}

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

@ -62,26 +62,26 @@
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showBarBorder">
{{ 'widgets.time-series-chart.series.bar.show-border' | translate }}
{{ 'widgets.chart.bar.show-border' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.bar.border-width</div>
<div translate>widgets.chart.bar.border-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="barBorderWidth" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.bar.border-radius</div>
<div translate>widgets.chart.bar.border-radius</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="barBorderRadius" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<tb-time-series-chart-fill-settings
<tb-chart-fill-settings
formControlName="barBackgroundSettings"
title="widgets.time-series-chart.series.background"
fillNoneTitle="widgets.time-series-chart.series.fill-type-solid">
</tb-time-series-chart-fill-settings>
title="widgets.chart.background"
fillNoneTitle="widgets.chart.fill-type-solid">
</tb-chart-fill-settings>
<tb-time-series-no-aggregation-bar-width-settings
stroked
formControlName="noAggregationBarWidthSettings">
@ -227,9 +227,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-echarts-animation-settings
<tb-chart-animation-settings
formControlName="animation">
</tb-echarts-animation-settings>
</tb-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">

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

@ -154,9 +154,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-echarts-animation-settings
<tb-chart-animation-settings
formControlName="animation">
</tb-echarts-animation-settings>
</tb-chart-animation-settings>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">

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

@ -172,9 +172,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-echarts-animation-settings
<tb-chart-animation-settings
formControlName="animation">
</tb-echarts-animation-settings>
</tb-chart-animation-settings>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">

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

@ -33,7 +33,7 @@ import {
import {
pieChartLabelPositions,
pieChartLabelPositionTranslations
} from '@home/components/widget/lib/chart/pie-chart.models';
} from '@home/components/widget/lib/chart/chart.models';
import {
pieChartWidgetDefaultSettings,
PieChartWidgetSettings

176
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.html

@ -0,0 +1,176 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ng-container [formGroup]="polarAreaChartWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.polar-area-chart.polar-area-chart-style</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="sortSeries">
{{ 'widgets.latest-chart.sort-series' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-appearance</div>
<tb-chart-bar-settings
formControlName="barSettings"
pieLabelPosition
[series]="false">
</tb-chart-bar-settings>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.polar-area-chart.polar-axis</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.chart.chart-axis.scale</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.chart.chart-axis.scale-min</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisMin"
type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.chart.chart-axis.scale-max</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisMax"
type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="axisTickLabelFont"
clearButton
disabledLineHeight
forceSizeUnit="px"
previewText="100">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="axisTickLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.polar-area-chart.start-angle</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="angleAxisStartAngle" type="number" min="0" max="360" 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]="polarAreaChartWidgetSettingsForm.get('showLegend').value" [disabled]="!polarAreaChartWidgetSettingsForm.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="Wind power">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'legend.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendValueFont"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendValueColor">
</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]="polarAreaChartWidgetSettingsForm.get('showTooltip').value" [disabled]="!polarAreaChartWidgetSettingsForm.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 column-xs">
<div>{{ 'tooltip.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="tooltipValueType">
<mat-option *ngFor="let type of latestChartTooltipValueTypes" [value]="type">
{{ latestChartTooltipValueTypeTranslationMap.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipValueDecimals" 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="tooltipValueFont"
[previewText]="tooltipValuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipValueColor">
</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>
<tb-chart-animation-settings
formControlName="animation">
</tb-chart-animation-settings>
<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>

161
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component.ts

@ -0,0 +1,161 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component } from '@angular/core';
import {
legendPositions,
legendPositionTranslationMap,
WidgetSettings,
WidgetSettingsComponent
} from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { formatValue, mergeDeep } from '@core/utils';
import {
LatestChartTooltipValueType,
latestChartTooltipValueTypes,
latestChartTooltipValueTypeTranslations
} from '@home/components/widget/lib/chart/latest-chart.models';
import {
polarAreaChartWidgetDefaultSettings,
PolarAreaChartWidgetSettings
} from '@home/components/widget/lib/chart/polar-area-widget.models';
@Component({
selector: 'tb-polar-area-chart-widget-settings',
templateUrl: './polar-area-chart-widget-settings.component.html',
styleUrls: []
})
export class PolarAreaChartWidgetSettingsComponent extends WidgetSettingsComponent {
legendPositions = legendPositions;
legendPositionTranslationMap = legendPositionTranslationMap;
latestChartTooltipValueTypes = latestChartTooltipValueTypes;
latestChartTooltipValueTypeTranslationMap = latestChartTooltipValueTypeTranslations;
polarAreaChartWidgetSettingsForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.polarAreaChartWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return mergeDeep<PolarAreaChartWidgetSettings>({} as PolarAreaChartWidgetSettings, polarAreaChartWidgetDefaultSettings);
}
protected onSettingsSet(settings: WidgetSettings) {
this.polarAreaChartWidgetSettingsForm = this.fb.group({
sortSeries: [settings.sortSeries, []],
barSettings: [settings.barSettings, []],
axisMin: [settings.axisMin, []],
axisMax: [settings.axisMax, []],
axisTickLabelFont: [settings.axisTickLabelFont, []],
axisTickLabelColor: [settings.axisTickLabelColor, []],
angleAxisStartAngle: [settings.angleAxisStartAngle, [Validators.min(0), Validators.max(360)]],
animation: [settings.animation, []],
showLegend: [settings.showLegend, []],
legendPosition: [settings.legendPosition, []],
legendLabelFont: [settings.legendLabelFont, []],
legendLabelColor: [settings.legendLabelColor, []],
legendValueFont: [settings.legendValueFont, []],
legendValueColor: [settings.legendValueColor, []],
showTooltip: [settings.showTooltip, []],
tooltipValueType: [settings.tooltipValueType, []],
tooltipValueDecimals: [settings.tooltipValueDecimals, []],
tooltipValueFont: [settings.tooltipValueFont, []],
tooltipValueColor: [settings.tooltipValueColor, []],
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []],
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
background: [settings.background, []]
});
}
protected validatorTriggers(): string[] {
return ['showLegend', 'showTooltip'];
}
protected updateValidators(emitEvent: boolean) {
const showLegend: boolean = this.polarAreaChartWidgetSettingsForm.get('showLegend').value;
const showTooltip: boolean = this.polarAreaChartWidgetSettingsForm.get('showTooltip').value;
if (showLegend) {
this.polarAreaChartWidgetSettingsForm.get('legendPosition').enable();
this.polarAreaChartWidgetSettingsForm.get('legendLabelFont').enable();
this.polarAreaChartWidgetSettingsForm.get('legendLabelColor').enable();
this.polarAreaChartWidgetSettingsForm.get('legendValueFont').enable();
this.polarAreaChartWidgetSettingsForm.get('legendValueColor').enable();
} else {
this.polarAreaChartWidgetSettingsForm.get('legendPosition').disable();
this.polarAreaChartWidgetSettingsForm.get('legendLabelFont').disable();
this.polarAreaChartWidgetSettingsForm.get('legendLabelColor').disable();
this.polarAreaChartWidgetSettingsForm.get('legendValueFont').disable();
this.polarAreaChartWidgetSettingsForm.get('legendValueColor').disable();
}
if (showTooltip) {
this.polarAreaChartWidgetSettingsForm.get('tooltipValueType').enable();
this.polarAreaChartWidgetSettingsForm.get('tooltipValueDecimals').enable();
this.polarAreaChartWidgetSettingsForm.get('tooltipValueFont').enable();
this.polarAreaChartWidgetSettingsForm.get('tooltipValueColor').enable();
this.polarAreaChartWidgetSettingsForm.get('tooltipBackgroundColor').enable();
this.polarAreaChartWidgetSettingsForm.get('tooltipBackgroundBlur').enable();
} else {
this.polarAreaChartWidgetSettingsForm.get('tooltipValueType').disable();
this.polarAreaChartWidgetSettingsForm.get('tooltipValueDecimals').disable();
this.polarAreaChartWidgetSettingsForm.get('tooltipValueFont').disable();
this.polarAreaChartWidgetSettingsForm.get('tooltipValueColor').disable();
this.polarAreaChartWidgetSettingsForm.get('tooltipBackgroundColor').disable();
this.polarAreaChartWidgetSettingsForm.get('tooltipBackgroundBlur').disable();
}
}
private _valuePreviewFn(): string {
const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals;
return formatValue(110, decimals, units, false);
}
private _tooltipValuePreviewFn(): string {
const tooltipValueType: LatestChartTooltipValueType = this.polarAreaChartWidgetSettingsForm.get('tooltipValueType').value;
const decimals: number = this.polarAreaChartWidgetSettingsForm.get('tooltipValueDecimals').value;
if (tooltipValueType === LatestChartTooltipValueType.percentage) {
return formatValue(35, decimals, '%', false);
} else {
const units: string = this.widgetConfig.config.units;
return formatValue(110, decimals, units, false);
}
}
}

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

@ -121,8 +121,8 @@
<div fxLayout="row" fxFlex.lt-md fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" fxFlex.lt-md appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointLabelPosition">
<mat-option *ngFor="let position of seriesLabelPositions" [value]="position">
{{ seriesLabelPositionTranslations.get(position) | translate }}
<mat-option *ngFor="let position of chartLabelPositions" [value]="position">
{{ chartLabelPositionTranslations.get(position) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
@ -151,8 +151,8 @@
<div translate>widgets.time-series-chart.series.point.point-shape</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointShape">
<mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ echartsShapeTranslations.get(shape) | translate }}
<mat-option *ngFor="let shape of chartShapes" [value]="shape">
{{ chartShapeTranslations.get(shape) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
@ -303,9 +303,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-echarts-animation-settings
<tb-chart-animation-settings
formControlName="animation">
</tb-echarts-animation-settings>
</tb-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">

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

@ -34,12 +34,10 @@ import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-s
import {
lineSeriesStepTypes,
lineSeriesStepTypeTranslations,
seriesLabelPositions,
seriesLabelPositionTranslations,
timeSeriesLineTypes,
timeSeriesLineTypeTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { echartsShapes, echartsShapeTranslations } from '@home/components/widget/lib/chart/echarts-widget.models';
import { chartLabelPositions, chartLabelPositionTranslations, chartShapes, chartShapeTranslations } from '@home/components/widget/lib/chart/chart.models';
@Component({
selector: 'tb-range-chart-widget-settings',
@ -65,13 +63,13 @@ export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent {
timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations;
seriesLabelPositions = seriesLabelPositions;
chartLabelPositions = chartLabelPositions;
seriesLabelPositionTranslations = seriesLabelPositionTranslations;
chartLabelPositionTranslations = chartLabelPositionTranslations;
echartsShapes = echartsShapes;
chartShapes = chartShapes;
echartsShapeTranslations = echartsShapeTranslations;
chartShapeTranslations = chartShapeTranslations;
legendPositions = legendPositions;
@ -164,7 +162,7 @@ export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent {
'showLegend', 'showTooltip', 'tooltipShowDate'];
}
protected updateValidators(emitEvent: boolean) {
protected updateValidators() {
const showRangeThresholds: boolean = this.rangeChartWidgetSettingsForm.get('showRangeThresholds').value;
const fillArea: boolean = this.rangeChartWidgetSettingsForm.get('fillArea').value;
const showLine: boolean = this.rangeChartWidgetSettingsForm.get('showLine').value;

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

@ -52,9 +52,9 @@
[chartType]="chartType"
formControlName="lineSettings">
</tb-time-series-chart-line-settings>
<tb-time-series-chart-bar-settings [fxShow]="timeSeriesChartKeySettingsForm.get('type').value === TimeSeriesChartSeriesType.bar"
formControlName="barSettings">
</tb-time-series-chart-bar-settings>
<tb-chart-bar-settings [fxShow]="timeSeriesChartKeySettingsForm.get('type').value === TimeSeriesChartSeriesType.bar"
formControlName="barSettings">
</tb-chart-bar-settings>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.chart.tooltip-settings</div>

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

@ -66,9 +66,9 @@
</div>
<ng-container *ngTemplateOutlet="pointSettings"></ng-container>
</div>
<tb-time-series-chart-fill-settings
<tb-chart-fill-settings
formControlName="fillAreaSettings">
</tb-time-series-chart-fill-settings>
</tb-chart-fill-settings>
</ng-container>
<ng-template #pointSettings [formGroup]="lineSettingsFormGroup">
<div class="tb-form-row space-between column-lt-md">
@ -80,8 +80,8 @@
<div fxLayout="row" fxFlex.lt-md fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" fxFlex.lt-md appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointLabelPosition">
<mat-option *ngFor="let position of seriesLabelPositions" [value]="position">
{{ seriesLabelPositionTranslations.get(position) | translate }}
<mat-option *ngFor="let position of chartLabelPositions" [value]="position">
{{ chartLabelPositionTranslations.get(position) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
@ -110,8 +110,8 @@
<div translate>widgets.time-series-chart.series.point.point-shape</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointShape">
<mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ echartsShapeTranslations.get(shape) | translate }}
<mat-option *ngFor="let shape of chartShapes" [value]="shape">
{{ chartShapeTranslations.get(shape) | translate }}
</mat-option>
</mat-select>
</mat-form-field>

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

@ -26,18 +26,21 @@ import {
LineSeriesSettings,
lineSeriesStepTypes,
lineSeriesStepTypeTranslations,
seriesLabelPositions,
seriesLabelPositionTranslations,
TimeSeriesChartType,
timeSeriesLineTypes,
timeSeriesLineTypeTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { echartsShapes, echartsShapeTranslations } from '@home/components/widget/lib/chart/echarts-widget.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { merge } from 'rxjs';
import { formatValue, isDefinedAndNotNull } from '@core/utils';
import { DataKeyConfigComponent } from '@home/components/widget/config/data-key-config.component';
import {
chartLabelPositions,
chartLabelPositionTranslations,
chartShapes,
chartShapeTranslations
} from '@home/components/widget/lib/chart/chart.models';
@Component({
selector: 'tb-time-series-chart-line-settings',
@ -63,13 +66,13 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu
timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations;
seriesLabelPositions = seriesLabelPositions;
chartLabelPositions = chartLabelPositions;
seriesLabelPositionTranslations = seriesLabelPositionTranslations;
chartLabelPositionTranslations = chartLabelPositionTranslations;
echartsShapes = echartsShapes;
chartShapes = chartShapes;
echartsShapeTranslations = echartsShapeTranslations;
chartShapeTranslations = chartShapeTranslations;
pointLabelPreviewFn = this._pointLabelPreviewFn.bind(this);

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

@ -254,9 +254,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-echarts-animation-settings
<tb-chart-animation-settings
formControlName="animation">
</tb-echarts-animation-settings>
</tb-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">

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

@ -28,12 +28,12 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { formatValue, isDefinedAndNotNull, mergeDeep } from '@core/utils';
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models';
import { EChartsTooltipTrigger } from '../../chart/echarts-widget.models';
import {
timeSeriesChartWidgetDefaultSettings,
TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import {
TimeSeriesChartTooltipTrigger,
TimeSeriesChartKeySettings,
TimeSeriesChartType,
TimeSeriesChartYAxes,
@ -65,7 +65,7 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
TimeSeriesChartType = TimeSeriesChartType;
EChartsTooltipTrigger = EChartsTooltipTrigger;
EChartsTooltipTrigger = TimeSeriesChartTooltipTrigger;
legendPositions = legendPositions;

20
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/echarts-animation-settings.component.html → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-animation-settings.component.html

@ -23,61 +23,61 @@
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="animation" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.echarts.animation.animation' | translate }}
{{ 'widgets.chart.animation.animation' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.echarts.animation.animation-threshold' | translate }}</div>
<div>{{ 'widgets.chart.animation.animation-threshold' | translate }}</div>
<mat-form-field appearance="outline" class="medium-width number" subscriptSizing="dynamic">
<input matInput formControlName="animationThreshold" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>ms</div>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.echarts.animation.animation-duration' | translate }}</div>
<div>{{ 'widgets.chart.animation.animation-duration' | translate }}</div>
<mat-form-field appearance="outline" class="medium-width number" subscriptSizing="dynamic">
<input matInput formControlName="animationDuration" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>ms</div>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.echarts.animation.animation-easing</div>
<div translate>widgets.chart.animation.animation-easing</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="animationEasing">
<mat-option *ngFor="let easing of echartsAnimationEasings" [value]="easing">
<mat-option *ngFor="let easing of chartAnimationEasings" [value]="easing">
{{ easing }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.echarts.animation.animation-delay' | translate }}</div>
<div>{{ 'widgets.chart.animation.animation-delay' | translate }}</div>
<mat-form-field appearance="outline" class="medium-width number" subscriptSizing="dynamic">
<input matInput formControlName="animationDelay" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>ms</div>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.echarts.animation.update-animation-duration' | translate }}</div>
<div>{{ 'widgets.chart.animation.update-animation-duration' | translate }}</div>
<mat-form-field appearance="outline" class="medium-width number" subscriptSizing="dynamic">
<input matInput formControlName="animationDurationUpdate" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>ms</div>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.echarts.animation.update-animation-easing</div>
<div translate>widgets.chart.animation.update-animation-easing</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="animationEasingUpdate">
<mat-option *ngFor="let easing of echartsAnimationEasings" [value]="easing">
<mat-option *ngFor="let easing of chartAnimationEasings" [value]="easing">
{{ easing }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.echarts.animation.update-animation-delay' | translate }}</div>
<div>{{ 'widgets.chart.animation.update-animation-delay' | translate }}</div>
<mat-form-field appearance="outline" class="medium-width number" subscriptSizing="dynamic">
<input matInput formControlName="animationDelayUpdate" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>ms</div>

23
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/echarts-animation-settings.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-animation-settings.component.ts

@ -22,41 +22,36 @@ import {
UntypedFormGroup,
Validators
} from '@angular/forms';
import { WidgetService } from '@core/http/widget.service';
import {
echartsAnimationEasings,
EChartsAnimationSettings
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { chartAnimationEasings, ChartAnimationSettings } from '@home/components/widget/lib/chart/chart.models';
@Component({
selector: 'tb-echarts-animation-settings',
templateUrl: './echarts-animation-settings.component.html',
selector: 'tb-chart-animation-settings',
templateUrl: './chart-animation-settings.component.html',
styleUrls: ['./../../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EchartsAnimationSettingsComponent),
useExisting: forwardRef(() => ChartAnimationSettingsComponent),
multi: true
}
]
})
export class EchartsAnimationSettingsComponent implements OnInit, ControlValueAccessor {
export class ChartAnimationSettingsComponent implements OnInit, ControlValueAccessor {
settingsExpanded = false;
echartsAnimationEasings = echartsAnimationEasings;
chartAnimationEasings = chartAnimationEasings;
@Input()
disabled: boolean;
private modelValue: EChartsAnimationSettings;
private modelValue: ChartAnimationSettings;
private propagateChange = null;
public animationSettingsFormGroup: UntypedFormGroup;
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService,) {
constructor(private fb: UntypedFormBuilder) {
}
ngOnInit(): void {
@ -96,7 +91,7 @@ export class EchartsAnimationSettingsComponent implements OnInit, ControlValueAc
}
}
writeValue(value: EChartsAnimationSettings): void {
writeValue(value: ChartAnimationSettings): void {
this.modelValue = value;
this.animationSettingsFormGroup.patchValue(
value, {emitEvent: false}

31
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-bar-settings.component.html

@ -18,32 +18,39 @@
<ng-container [formGroup]="barSettingsFormGroup">
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showBorder">
{{ 'widgets.time-series-chart.series.bar.show-border' | translate }}
{{ 'widgets.chart.bar.show-border' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.bar.border-width</div>
<div translate>widgets.chart.bar.border-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="borderWidth" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.bar.border-radius</div>
<div translate>widgets.chart.bar.border-radius</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="borderRadius" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<div *ngIf="!series" class="tb-form-row space-between column-xs">
<div translate>widgets.chart.bar.bar-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="barWidth" min="0" max="100" placeholder="{{ 'widget-config.set' | translate }}"/>
<div matSuffix>%</div>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-lt-md">
<mat-slide-toggle class="mat-slide" formControlName="showLabel">
<div tb-hint-tooltip-icon="{{'widgets.time-series-chart.series.bar.label-hint' | translate}}">
{{ 'widgets.time-series-chart.series.bar.label' | translate }}
<div tb-hint-tooltip-icon="{{(series ? 'widgets.chart.bar.series-label-hint' : 'widgets.chart.bar.label-hint') | translate}}">
{{ 'widgets.chart.bar.label' | translate }}
</div>
</mat-slide-toggle>
<div fxLayout="row" fxFlex.lt-md fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" fxFlex.lt-md appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="labelPosition">
<mat-option *ngFor="let position of seriesLabelPositions" [value]="position">
{{ seriesLabelPositionTranslations.get(position) | translate }}
<mat-option *ngFor="let position of chartLabelPositions" [value]="position">
{{ chartLabelPositionTranslations.get(position) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
@ -61,16 +68,16 @@
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="enableLabelBackground">
{{ 'widgets.time-series-chart.series.bar.label-background' | translate }}
{{ 'widgets.chart.bar.label-background' | translate }}
</mat-slide-toggle>
<tb-color-input asBoxInput
colorClearButton
formControlName="labelBackground">
</tb-color-input>
</div>
<tb-time-series-chart-fill-settings
<tb-chart-fill-settings
formControlName="backgroundSettings"
title="widgets.time-series-chart.series.background"
fillNoneTitle="widgets.time-series-chart.series.fill-type-solid">
</tb-time-series-chart-fill-settings>
title="widgets.chart.background"
fillNoneTitle="widgets.chart.fill-type-solid">
</tb-chart-fill-settings>
</ng-container>

72
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-bar-settings.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { Component, forwardRef, Input, OnInit, Optional } from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
@ -22,52 +22,72 @@ import {
UntypedFormGroup,
Validators
} from '@angular/forms';
import {
BarSeriesSettings,
seriesLabelPositions,
seriesLabelPositionTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { merge } from 'rxjs';
import { formatValue, isDefinedAndNotNull } from '@core/utils';
import { DataKeyConfigComponent } from '@home/components/widget/config/data-key-config.component';
import {
ChartBarSettings,
ChartLabelPosition,
chartLabelPositions,
chartLabelPositionTranslations,
PieChartLabelPosition,
pieChartLabelPositions,
pieChartLabelPositionTranslations
} from '@home/components/widget/lib/chart/chart.models';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-time-series-chart-bar-settings',
templateUrl: './time-series-chart-bar-settings.component.html',
styleUrls: ['./../widget-settings.scss'],
selector: 'tb-chart-bar-settings',
templateUrl: './chart-bar-settings.component.html',
styleUrls: ['./../../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TimeSeriesChartBarSettingsComponent),
useExisting: forwardRef(() => ChartBarSettingsComponent),
multi: true
}
]
})
export class TimeSeriesChartBarSettingsComponent implements OnInit, ControlValueAccessor {
export class ChartBarSettingsComponent implements OnInit, ControlValueAccessor {
seriesLabelPositions = seriesLabelPositions;
chartLabelPositions: (ChartLabelPosition | PieChartLabelPosition)[];
seriesLabelPositionTranslations = seriesLabelPositionTranslations;
chartLabelPositionTranslations: Map<ChartLabelPosition | PieChartLabelPosition, string>;
labelPreviewFn = this._labelPreviewFn.bind(this);
@Input()
disabled: boolean;
private modelValue: BarSeriesSettings;
@Input()
@coerceBoolean()
series = true;
@Input()
@coerceBoolean()
pieLabelPosition = false;
private modelValue: ChartBarSettings;
private propagateChange = null;
public barSettingsFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
private dataKeyConfigComponent: DataKeyConfigComponent,
@Optional() private dataKeyConfigComponent: DataKeyConfigComponent,
private fb: UntypedFormBuilder) {
}
ngOnInit(): void {
if (this.pieLabelPosition) {
this.chartLabelPositions = pieChartLabelPositions;
this.chartLabelPositionTranslations = pieChartLabelPositionTranslations;
} else {
this.chartLabelPositions = chartLabelPositions;
this.chartLabelPositionTranslations = chartLabelPositionTranslations;
}
this.barSettingsFormGroup = this.fb.group({
showBorder: [null, []],
borderWidth: [null, [Validators.min(0)]],
@ -80,6 +100,10 @@ export class TimeSeriesChartBarSettingsComponent implements OnInit, ControlValue
labelBackground: [null, []],
backgroundSettings: [null, []]
});
if (!this.series) {
this.barSettingsFormGroup.addControl('barWidth', this.fb.control(null,
[Validators.min(0), Validators.max(100)]));
}
this.barSettingsFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
@ -108,7 +132,7 @@ export class TimeSeriesChartBarSettingsComponent implements OnInit, ControlValue
}
}
writeValue(value: BarSeriesSettings): void {
writeValue(value: ChartBarSettings): void {
this.modelValue = value;
this.barSettingsFormGroup.patchValue(
value, {emitEvent: false}
@ -150,11 +174,15 @@ export class TimeSeriesChartBarSettingsComponent implements OnInit, ControlValue
}
private _labelPreviewFn(): string {
const dataKey = this.dataKeyConfigComponent.modelValue;
const widgetConfig = this.dataKeyConfigComponent.widgetConfig;
const units = dataKey.units && dataKey.units.length ? dataKey.units : widgetConfig.config.units;
const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals :
(isDefinedAndNotNull(widgetConfig.config.decimals) ? widgetConfig.config.decimals : 2);
return formatValue(22, decimals, units, false);
if (this.series) {
const dataKey = this.dataKeyConfigComponent.modelValue;
const widgetConfig = this.dataKeyConfigComponent.widgetConfig;
const units = dataKey.units && dataKey.units.length ? dataKey.units : widgetConfig.config.units;
const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals :
(isDefinedAndNotNull(widgetConfig.config.decimals) ? widgetConfig.config.decimals : 2);
return formatValue(22, decimals, units, false);
} else {
return 'Wind';
}
}
}

14
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.html → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html

@ -20,28 +20,28 @@
<div class="tb-form-row no-border no-padding space-between">
<div>{{ title | translate }}</div>
<tb-toggle-select formControlName="type">
<tb-toggle-option *ngFor="let type of seriesFillTypes" [value]="type">{{ seriesFillTypeTranslationMap.get(type) | translate }}</tb-toggle-option>
<tb-toggle-option *ngFor="let type of chartFillTypes" [value]="type">{{ chartFillTypeTranslationMap.get(type) | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<ng-container *ngIf="fillSettingsFormGroup.get('type').value === SeriesFillType.opacity">
<ng-container *ngIf="fillSettingsFormGroup.get('type').value === ChartFillType.opacity">
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.opacity</div>
<div translate>widgets.chart.opacity</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="opacity" min="0" max="1"
step="0.1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
</ng-container>
<ng-container *ngIf="fillSettingsFormGroup.get('type').value === SeriesFillType.gradient" formGroupName="gradient">
<ng-container *ngIf="fillSettingsFormGroup.get('type').value === ChartFillType.gradient" formGroupName="gradient">
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.gradient-stops</div>
<div translate>widgets.chart.gradient-stops</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.time-series-chart.series.gradient-start</div>
<div class="tb-small-label" translate>widgets.chart.gradient-start</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="start" min="0" max="100" step="1"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.time-series-chart.series.gradient-end</div>
<div class="tb-small-label" translate>widgets.chart.gradient-end</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="end" min="0" max="100" step="1"
type="number" placeholder="{{ 'widget-config.set' | translate }}">

52
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts

@ -22,45 +22,45 @@ import {
UntypedFormGroup,
Validators
} from '@angular/forms';
import {
SeriesFillSettings,
SeriesFillType,
seriesFillTypes,
seriesFillTypeTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import {
ChartFillSettings,
ChartFillType,
chartFillTypes,
chartFillTypeTranslations
} from '@home/components/widget/lib/chart/chart.models';
@Component({
selector: 'tb-time-series-chart-fill-settings',
templateUrl: './time-series-chart-fill-settings.component.html',
selector: 'tb-chart-fill-settings',
templateUrl: './chart-fill-settings.component.html',
styleUrls: ['./../../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TimeSeriesChartFillSettingsComponent),
useExisting: forwardRef(() => ChartFillSettingsComponent),
multi: true
}
]
})
export class TimeSeriesChartFillSettingsComponent implements OnInit, ControlValueAccessor {
export class ChartFillSettingsComponent implements OnInit, ControlValueAccessor {
seriesFillTypes = seriesFillTypes;
chartFillTypes = chartFillTypes;
seriesFillTypeTranslationMap: Map<SeriesFillType, string> = new Map<SeriesFillType, string>([]);
chartFillTypeTranslationMap: Map<ChartFillType, string> = new Map<ChartFillType, string>([]);
SeriesFillType = SeriesFillType;
ChartFillType = ChartFillType;
@Input()
disabled: boolean;
@Input()
title = 'widgets.time-series-chart.series.fill';
title = 'widgets.chart.fill';
@Input()
fillNoneTitle = 'widgets.time-series-chart.series.fill-type-none';
fillNoneTitle = 'widgets.chart.fill-type-none';
private modelValue: SeriesFillSettings;
private modelValue: ChartFillSettings;
private propagateChange = null;
@ -85,14 +85,14 @@ export class TimeSeriesChartFillSettingsComponent implements OnInit, ControlValu
this.fillSettingsFormGroup.get('type').valueChanges.subscribe(() => {
this.updateValidators();
});
for (const type of seriesFillTypes) {
for (const type of chartFillTypes) {
let translation: string;
if (type === SeriesFillType.none) {
if (type === ChartFillType.none) {
translation = this.fillNoneTitle;
} else {
translation = seriesFillTypeTranslations.get(type);
translation = chartFillTypeTranslations.get(type);
}
this.seriesFillTypeTranslationMap.set(type, translation);
this.chartFillTypeTranslationMap.set(type, translation);
}
}
@ -113,7 +113,7 @@ export class TimeSeriesChartFillSettingsComponent implements OnInit, ControlValu
}
}
writeValue(value: SeriesFillSettings): void {
writeValue(value: ChartFillSettings): void {
this.modelValue = value;
this.fillSettingsFormGroup.patchValue(
value, {emitEvent: false}
@ -122,21 +122,21 @@ export class TimeSeriesChartFillSettingsComponent implements OnInit, ControlValu
}
private updateValidators() {
const type: SeriesFillType = this.fillSettingsFormGroup.get('type').value;
if (type === SeriesFillType.none) {
const type: ChartFillType = this.fillSettingsFormGroup.get('type').value;
if (type === ChartFillType.none) {
this.fillSettingsFormGroup.get('opacity').disable({emitEvent: false});
this.fillSettingsFormGroup.get('gradient').disable({emitEvent: false});
} else if (type === SeriesFillType.opacity) {
} else if (type === ChartFillType.opacity) {
this.fillSettingsFormGroup.get('opacity').enable({emitEvent: false});
this.fillSettingsFormGroup.get('gradient').disable({emitEvent: false});
} else if (type === SeriesFillType.gradient) {
} else if (type === ChartFillType.gradient) {
this.fillSettingsFormGroup.get('opacity').disable({emitEvent: false});
this.fillSettingsFormGroup.get('gradient').enable({emitEvent: false});
}
}
private updateModel() {
const value: SeriesFillSettings = this.fillSettingsFormGroup.getRawValue();
const value: ChartFillSettings = this.fillSettingsFormGroup.getRawValue();
this.modelValue = value;
this.propagateChange(this.modelValue);
}

14
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html

@ -145,7 +145,7 @@
</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="interval"
type="number" min="0" placeholder="{{ 'widgets.time-series-chart.axis.scale-auto' | translate }}">
type="number" min="0" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
</div>
<div *ngIf="axisType === 'yAxis'" class="tb-form-row space-between">
@ -154,24 +154,24 @@
</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="splitNumber"
type="number" min="1" placeholder="{{ 'widgets.time-series-chart.axis.scale-auto' | translate }}">
type="number" min="1" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
<div *ngIf="axisType === 'yAxis'" class="tb-form-panel no-border no-padding-top">
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.axis.scale</div>
<div translate>widgets.chart.chart-axis.scale</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.time-series-chart.axis.scale-min</div>
<div class="tb-small-label" translate>widgets.chart.chart-axis.scale-min</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="min"
type="number" placeholder="{{ 'widgets.time-series-chart.axis.scale-auto' | translate }}">
type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.time-series-chart.axis.scale-max</div>
<div class="tb-small-label" translate>widgets.chart.chart-axis.scale-max</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="max"
type="number" placeholder="{{ 'widgets.time-series-chart.axis.scale-auto' | translate }}">
type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
</div>
</div>

8
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.html

@ -103,8 +103,8 @@
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="startSymbol">
<mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ echartsShapeTranslations.get(shape) | translate }}
<mat-option *ngFor="let shape of chartShapes" [value]="shape">
{{ chartShapeTranslations.get(shape) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
@ -120,8 +120,8 @@
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="endSymbol">
<mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ echartsShapeTranslations.get(shape) | translate }}
<mat-option *ngFor="let shape of chartShapes" [value]="shape">
{{ chartShapeTranslations.get(shape) | translate }}
</mat-option>
</mat-select>
</mat-form-field>

18
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts

@ -25,15 +25,11 @@ import {
timeSeriesThresholdLabelPositions,
timeSeriesThresholdLabelPositionTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
import {
EChartsShape,
echartsShapes,
echartsShapeTranslations
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { merge } from 'rxjs';
import { WidgetConfig } from '@shared/models/widget.models';
import { formatValue, isDefinedAndNotNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
import { ChartShape, chartShapes, chartShapeTranslations } from '@home/components/widget/lib/chart/chart.models';
@Component({
selector: 'tb-time-series-chart-threshold-settings-panel',
@ -48,9 +44,9 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations;
echartsShapes = echartsShapes;
chartShapes = chartShapes;
echartsShapeTranslations = echartsShapeTranslations;
chartShapeTranslations = chartShapeTranslations;
timeSeriesThresholdLabelPositions = timeSeriesThresholdLabelPositions;
@ -127,8 +123,8 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
private updateValidators() {
const showLabel: boolean = this.thresholdSettingsFormGroup.get('showLabel').value;
const enableLabelBackground: boolean = this.thresholdSettingsFormGroup.get('enableLabelBackground').value;
const startSymbol: EChartsShape = this.thresholdSettingsFormGroup.get('startSymbol').value;
const endSymbol: EChartsShape = this.thresholdSettingsFormGroup.get('endSymbol').value;
const startSymbol: ChartShape = this.thresholdSettingsFormGroup.get('startSymbol').value;
const endSymbol: ChartShape = this.thresholdSettingsFormGroup.get('endSymbol').value;
if (showLabel) {
this.thresholdSettingsFormGroup.get('labelPosition').enable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelFont').enable({emitEvent: false});
@ -146,12 +142,12 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
this.thresholdSettingsFormGroup.get('enableLabelBackground').disable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelBackground').disable({emitEvent: false});
}
if (startSymbol === EChartsShape.none) {
if (startSymbol === ChartShape.none) {
this.thresholdSettingsFormGroup.get('startSymbolSize').disable({emitEvent: false});
} else {
this.thresholdSettingsFormGroup.get('startSymbolSize').enable({emitEvent: false});
}
if (endSymbol === EChartsShape.none) {
if (endSymbol === ChartShape.none) {
this.thresholdSettingsFormGroup.get('endSymbolSize').disable({emitEvent: false});
} else {
this.thresholdSettingsFormGroup.get('endSymbolSize').enable({emitEvent: false});

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html

@ -32,12 +32,12 @@
</mat-form-field>
<div class="tb-min-field">
<mat-form-field appearance="outline" class="tb-inline-field number" subscriptSizing="dynamic">
<input matInput formControlName="min" type="number" placeholder="{{ 'widgets.time-series-chart.axis.scale-auto' | translate }}">
<input matInput formControlName="min" type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
</div>
<div class="tb-max-field">
<mat-form-field appearance="outline" class="tb-inline-field number" subscriptSizing="dynamic">
<input matInput formControlName="max" type="number" placeholder="{{ 'widgets.time-series-chart.axis.scale-auto' | translate }}">
<input matInput formControlName="max" type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}">
</mat-form-field>
</div>
<div class="tb-units-field">

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

@ -119,8 +119,8 @@ import {
TimeSeriesChartAxisSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component';
import {
EchartsAnimationSettingsComponent
} from '@home/components/widget/lib/settings/common/chart/echarts-animation-settings.component';
ChartAnimationSettingsComponent
} from '@home/components/widget/lib/settings/common/chart/chart-animation-settings.component';
import {
AutoDateFormatSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/auto-date-format-settings-panel.component';
@ -128,8 +128,8 @@ import {
AutoDateFormatSettingsComponent
} from '@home/components/widget/lib/settings/common/auto-date-format-settings.component';
import {
TimeSeriesChartFillSettingsComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component';
ChartFillSettingsComponent
} from '@home/components/widget/lib/settings/common/chart/chart-fill-settings.component';
import {
TimeSeriesChartThresholdSettingsComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component';
@ -148,6 +148,7 @@ import {
import {
StatusWidgetStateSettingsComponent
} from '@home/components/widget/lib/settings/common/indicator/status-widget-state-settings.component';
import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/common/chart/chart-bar-settings.component';
@NgModule({
declarations: [
@ -195,8 +196,9 @@ import {
TimeSeriesChartYAxisRowComponent,
TimeSeriesChartAxisSettingsPanelComponent,
TimeSeriesChartAxisSettingsButtonComponent,
EchartsAnimationSettingsComponent,
TimeSeriesChartFillSettingsComponent,
ChartAnimationSettingsComponent,
ChartFillSettingsComponent,
ChartBarSettingsComponent,
TimeSeriesChartThresholdSettingsComponent,
TimeSeriesChartStatesPanelComponent,
TimeSeriesChartStateRowComponent,
@ -255,8 +257,9 @@ import {
TimeSeriesChartYAxisRowComponent,
TimeSeriesChartAxisSettingsPanelComponent,
TimeSeriesChartAxisSettingsButtonComponent,
EchartsAnimationSettingsComponent,
TimeSeriesChartFillSettingsComponent,
ChartAnimationSettingsComponent,
ChartFillSettingsComponent,
ChartBarSettingsComponent,
TimeSeriesChartThresholdSettingsComponent,
TimeSeriesChartStatesPanelComponent,
TimeSeriesChartStateRowComponent,

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

@ -336,9 +336,6 @@ import {
import {
TimeSeriesChartLineSettingsComponent
} from '@home/components/widget/lib/settings/chart/time-series-chart-line-settings.component';
import {
TimeSeriesChartBarSettingsComponent
} from '@home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component';
import {
TimeSeriesChartWidgetSettingsComponent
} from '@home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component';
@ -348,6 +345,12 @@ import {
import {
PieChartWidgetSettingsComponent
} from '@home/components/widget/lib/settings/chart/pie-chart-widget-settings.component';
import {
BarChartWidgetSettingsComponent
} from '@home/components/widget/lib/settings/chart/bar-chart-widget-settings.component';
import {
PolarAreaChartWidgetSettingsComponent
} from '@home/components/widget/lib/settings/chart/polar-area-chart-widget-settings.component';
@NgModule({
declarations: [
@ -469,10 +472,11 @@ import {
ToggleButtonWidgetSettingsComponent,
TimeSeriesChartKeySettingsComponent,
TimeSeriesChartLineSettingsComponent,
TimeSeriesChartBarSettingsComponent,
TimeSeriesChartWidgetSettingsComponent,
StatusWidgetSettingsComponent,
PieChartWidgetSettingsComponent
PieChartWidgetSettingsComponent,
BarChartWidgetSettingsComponent,
PolarAreaChartWidgetSettingsComponent
],
imports: [
CommonModule,
@ -599,10 +603,11 @@ import {
ToggleButtonWidgetSettingsComponent,
TimeSeriesChartKeySettingsComponent,
TimeSeriesChartLineSettingsComponent,
TimeSeriesChartBarSettingsComponent,
TimeSeriesChartWidgetSettingsComponent,
StatusWidgetSettingsComponent,
PieChartWidgetSettingsComponent
PieChartWidgetSettingsComponent,
BarChartWidgetSettingsComponent,
PolarAreaChartWidgetSettingsComponent
]
})
export class WidgetSettingsModule {
@ -697,5 +702,7 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-time-series-chart-key-settings': TimeSeriesChartKeySettingsComponent,
'tb-time-series-chart-widget-settings': TimeSeriesChartWidgetSettingsComponent,
'tb-status-widget-settings': StatusWidgetSettingsComponent,
'tb-pie-chart-widget-settings': PieChartWidgetSettingsComponent
'tb-pie-chart-widget-settings': PieChartWidgetSettingsComponent,
'tb-bar-chart-widget-settings': BarChartWidgetSettingsComponent,
'tb-polar-area-chart-widget-settings': PolarAreaChartWidgetSettingsComponent
};

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

@ -89,6 +89,8 @@ import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/char
import { StatusWidgetComponent } from '@home/components/widget/lib/indicator/status-widget.component';
import { LatestChartComponent } from '@home/components/widget/lib/chart/latest-chart.component';
import { PieChartWidgetComponent } from '@home/components/widget/lib/chart/pie-chart-widget.component';
import { BarChartWidgetComponent } from '@home/components/widget/lib/chart/bar-chart-widget.component';
import { PolarAreaWidgetComponent } from '@home/components/widget/lib/chart/polar-area-widget.component';
@NgModule({
declarations:
@ -144,7 +146,9 @@ import { PieChartWidgetComponent } from '@home/components/widget/lib/chart/pie-c
TimeSeriesChartWidgetComponent,
StatusWidgetComponent,
LatestChartComponent,
PieChartWidgetComponent
PieChartWidgetComponent,
BarChartWidgetComponent,
PolarAreaWidgetComponent
],
imports: [
CommonModule,
@ -203,7 +207,9 @@ import { PieChartWidgetComponent } from '@home/components/widget/lib/chart/pie-c
ToggleButtonWidgetComponent,
TimeSeriesChartWidgetComponent,
StatusWidgetComponent,
PieChartWidgetComponent
PieChartWidgetComponent,
BarChartWidgetComponent,
PolarAreaWidgetComponent
],
providers: [
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }

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

@ -5429,7 +5429,13 @@
"bar-appearance": "Bar appearance",
"label-on-bar": "Label on bar",
"value-on-bar": "Value on bar",
"bar-chart-style": "Bar chart style"
"bar-chart-style": "Bar chart style",
"bar-axis": "Bar axis"
},
"polar-area-chart": {
"polar-axis": "Polar axis",
"start-angle": "Start angle",
"polar-area-chart-style": "Polar area chart style"
},
"battery-level": {
"layout": "Layout",
@ -5609,7 +5615,56 @@
"axis": "Axis",
"vertical-axis": "Vertical axis",
"ticks": "Ticks",
"horizontal-axis": "Horizontal axis"
"horizontal-axis": "Horizontal axis",
"shape-empty-circle": "Empty circle",
"shape-circle": "Circle",
"shape-rect": "Rectangle",
"shape-round-rect": "Rounded rectangle",
"shape-triangle": "Triangle",
"shape-diamond": "Diamond",
"shape-pin": "Pin",
"shape-arrow": "Arrow",
"shape-none": "None",
"label-position-top": "Top",
"label-position-bottom": "Bottom",
"label-position-outside": "Outside",
"label-position-inside": "Inside",
"fill": "Fill",
"fill-type-none": "None",
"fill-type-solid": "Solid",
"fill-type-opacity": "Opacity",
"fill-type-gradient": "Gradient",
"background": "Background",
"opacity": "Opacity",
"gradient-stops": "Gradient stops",
"gradient-start": "start",
"gradient-end": "end",
"animation": {
"animation": "Animation",
"animation-threshold": "Animation threshold",
"animation-duration": "Animation duration",
"animation-easing": "Animation easing",
"animation-delay": "Animation delay",
"update-animation-duration": "Update animation duration",
"update-animation-easing": "Update animation easing",
"update-animation-delay": "Update animation delay"
},
"chart-axis": {
"scale": "Scale",
"scale-min": "min",
"scale-max": "max",
"scale-auto": "Auto"
},
"bar": {
"show-border": "Show border",
"border-width": "Border width",
"border-radius": "Border radius",
"bar-width": "Bar width",
"label": "Label",
"label-hint": "Display label over the bar.",
"series-label-hint": "Display label with value over the bar.",
"label-background": "Label background"
}
},
"color": {
"color-settings": "Color settings",
@ -6730,18 +6785,6 @@
"table-tabs": "Table tabs",
"show-cell-actions-menu-mobile": "Show cell actions dropdown menu in mobile mode"
},
"echarts": {
"animation": {
"animation": "Animation",
"animation-threshold": "Animation threshold",
"animation-duration": "Animation duration",
"animation-easing": "Animation easing",
"animation-delay": "Animation delay",
"update-animation-duration": "Update animation duration",
"update-animation-easing": "Update animation easing",
"update-animation-delay": "Update animation delay"
}
},
"latest-chart": {
"total": "Total",
"auto-scale": "Auto scale",
@ -6752,8 +6795,6 @@
},
"pie-chart": {
"label": "Label",
"label-position-outside": "Outside",
"label-position-inside": "Inside",
"border": "Border",
"radius": "Radius",
"pie-chart-card-style": "Pie chart card style"
@ -6771,15 +6812,6 @@
"line-type-dashed": "Dashed",
"line-type-dotted": "Dotted",
"line-width": "Line width",
"shape-empty-circle": "Empty circle",
"shape-circle": "Circle",
"shape-rect": "Rectangle",
"shape-round-rect": "Rounded rectangle",
"shape-triangle": "Triangle",
"shape-diamond": "Diamond",
"shape-pin": "Pin",
"shape-arrow": "Arrow",
"shape-none": "None",
"type-line": "Line",
"type-bar": "Bar",
"type-point": "Point",
@ -6878,10 +6910,6 @@
"ticks-interval-hint": "Compulsively set segmentation interval for axis.",
"split-number": "Split number",
"split-number-hint": "Number of segments that the axis is split into.",
"scale": "Scale",
"scale-min": "min",
"scale-max": "max",
"scale-auto": "Auto",
"min": "Min",
"max": "Max",
"show": "Show",
@ -6897,18 +6925,6 @@
"type": "Type",
"type-line": "Line",
"type-bar": "Bar",
"label-position-top": "Top",
"label-position-bottom": "Bottom",
"fill": "Fill",
"background": "Background",
"fill-type-none": "None",
"fill-type-solid": "Solid",
"fill-type-opacity": "Opacity",
"fill-type-gradient": "Gradient",
"opacity": "Opacity",
"gradient-stops": "Gradient stops",
"gradient-start": "start",
"gradient-end": "end",
"line": {
"line": "Line",
"show-line": "Show line",
@ -6926,14 +6942,6 @@
"point-label-background": "Point label background",
"point-shape": "Point shape",
"point-size": "Point size"
},
"bar": {
"show-border": "Show border",
"border-width": "Border width",
"border-radius": "Border radius",
"label": "Label",
"label-hint": "Display label with value over the bar.",
"label-background": "Label background"
}
}
},

Loading…
Cancel
Save