Browse Source

UI: Improve time series charts X axis and tooltip date formatting: introduce auto date format.

pull/10368/head
Igor Kulikov 3 years ago
parent
commit
60f40ad6b8
  1. 44
      application/src/main/data/json/demo/dashboards/thermostats.json
  2. 2
      application/src/main/data/json/system/widget_types/bar_chart.json
  3. 2
      application/src/main/data/json/system/widget_types/bar_chart_with_labels.json
  4. 2
      application/src/main/data/json/system/widget_types/line_chart.json
  5. 2
      application/src/main/data/json/system/widget_types/point_chart.json
  6. 5
      application/src/main/data/json/system/widget_types/range_chart.json
  7. 2
      application/src/main/data/json/system/widget_types/time_series_chart.json
  8. 3
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html
  9. 3
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html
  10. 11
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html
  11. 3
      ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts
  12. 3
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts
  13. 9
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
  14. 12
      ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts
  15. 8
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts
  16. 108
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  17. 30
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  18. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html
  19. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html
  20. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html
  21. 49
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.html
  22. 73
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.scss
  23. 92
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.ts
  24. 25
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.html
  25. 99
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.ts
  26. 22
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html
  27. 18
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts
  28. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.html
  29. 49
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts
  30. 10
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  31. 157
      ui-ngx/src/app/shared/models/widget-settings.models.ts
  32. 14
      ui-ngx/src/assets/dashboard/sys_admin_home_page.json
  33. 14
      ui-ngx/src/assets/dashboard/tenant_admin_home_page.json
  34. 15
      ui-ngx/src/assets/locale/locale.constant-en_US.json

44
application/src/main/data/json/demo/dashboards/thermostats.json

@ -1147,7 +1147,8 @@
"showLine": true,
"lineColor": "rgba(0, 0, 0, 0.54)",
"showSplitLines": true,
"splitLinesColor": "rgba(0, 0, 0, 0.12)"
"splitLinesColor": "rgba(0, 0, 0, 0.12)",
"ticksFormat": {}
},
"legendLabelFont": {
"family": "Roboto",
@ -1173,7 +1174,9 @@
"tooltipDateFormat": {
"format": "MMM dd yyyy HH:mm",
"lastUpdateAgo": false,
"custom": false
"custom": false,
"auto": true,
"autoDateFormatSettings": {}
},
"tooltipDateFont": {
"family": "Roboto",
@ -1537,7 +1540,8 @@
"showLine": true,
"lineColor": "rgba(0, 0, 0, 0.54)",
"showSplitLines": true,
"splitLinesColor": "rgba(0, 0, 0, 0.12)"
"splitLinesColor": "rgba(0, 0, 0, 0.12)",
"ticksFormat": {}
},
"legendLabelFont": {
"family": "Roboto",
@ -1561,9 +1565,11 @@
"tooltipValueColor": "rgba(0, 0, 0, 0.76)",
"tooltipShowDate": true,
"tooltipDateFormat": {
"format": "MMM dd yyyy HH:mm",
"format": null,
"lastUpdateAgo": false,
"custom": false
"custom": false,
"auto": true,
"autoDateFormatSettings": {}
},
"tooltipDateFont": {
"family": "Roboto",
@ -1712,19 +1718,25 @@
"sizeX": 11,
"sizeY": 11,
"row": 0,
"col": 0
"col": 0,
"mobileOrder": 1,
"mobileHeight": 5
},
"7943196b-eedb-d422-f9c3-b32d379ad172": {
"sizeX": 13,
"sizeY": 5,
"row": 0,
"col": 11
"col": 11,
"mobileOrder": 2,
"mobileHeight": 5
},
"3da9a9a1-0b9a-2e1f-0dcb-0ff34a695abb": {
"sizeX": 13,
"sizeY": 6,
"row": 5,
"col": 11
"col": 11,
"mobileOrder": 3,
"mobileHeight": 5
}
},
"gridSettings": {
@ -1778,25 +1790,33 @@
"sizeX": 6,
"sizeY": 6,
"row": 0,
"col": 0
"col": 0,
"mobileOrder": 3,
"mobileHeight": 5
},
"0a430429-9078-9ae6-2b67-e4a15a2bf8bf": {
"sizeX": 6,
"sizeY": 6,
"row": 6,
"col": 0
"col": 0,
"mobileOrder": 4,
"mobileHeight": 6
},
"eda8a397-0959-690c-405c-11e2c9b2bc7e": {
"sizeX": 18,
"sizeY": 6,
"row": 0,
"col": 6
"col": 6,
"mobileOrder": 1,
"mobileHeight": 6
},
"ac90f089-197f-b767-82c3-2668844265a2": {
"sizeX": 18,
"sizeY": 6,
"row": 6,
"col": 6
"col": 6,
"mobileOrder": 2,
"mobileHeight": 6
}
},
"gridSettings": {

2
application/src/main/data/json/system/widget_types/bar_chart.json

File diff suppressed because one or more lines are too long

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

@ -20,7 +20,7 @@
"latestDataKeySettingsDirective": "",
"hasBasicMode": true,
"basicModeDirective": "tb-bar-chart-with-labels-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"rgb(125, 142, 255)\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 50) {\\n\\tvalue = 50;\\n} else if (value > 80) {\\n\\tvalue = 80;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Soil moisture\",\"color\":\"rgb(249, 111, 255)\",\"settings\":{},\"_hash\":0.9111685461089025,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 30) {\\n\\tvalue = 30;\\n} else if (value > 90) {\\n\\tvalue = 90;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Leaf wetness\",\"color\":\"rgb(255, 163, 137)\",\"settings\":{},\"_hash\":0.8487533373085416,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 40) {\\n\\tvalue = 40;\\n} else if (value > 70) {\\n\\tvalue = 70;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cloud cover\",\"color\":\"#FFED53\",\"settings\":{},\"_hash\":0.7690144858984289,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 20) {\\n\\tvalue = 20;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":1,\"history\":{\"historyType\":2,\"timewindowMs\":60000,\"interval\":\"MONTH\",\"fixedTimewindow\":{\"startTimeMs\":1704293713163,\"endTimeMs\":1704380113163},\"quickInterval\":\"CURRENT_HALF_YEAR\"},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showBarLabel\":true,\"barLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"12px\"},\"barLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showBarValue\":true,\"barValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"700\",\"lineHeight\":\"12px\"},\"barValueColor\":\"rgba(0, 0, 0, 0.76)\",\"showLegend\":true,\"legendPosition\":\"top\",\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":\"MMMM y\",\"lastUpdateAgo\":false,\"custom\":true},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Bar chart with labels\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"public\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"%\",\"decimals\":0,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"24px\"}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"rgb(125, 142, 255)\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 50) {\\n\\tvalue = 50;\\n} else if (value > 80) {\\n\\tvalue = 80;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Soil moisture\",\"color\":\"rgb(249, 111, 255)\",\"settings\":{},\"_hash\":0.9111685461089025,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 30) {\\n\\tvalue = 30;\\n} else if (value > 90) {\\n\\tvalue = 90;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Leaf wetness\",\"color\":\"rgb(255, 163, 137)\",\"settings\":{},\"_hash\":0.8487533373085416,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 40) {\\n\\tvalue = 40;\\n} else if (value > 70) {\\n\\tvalue = 70;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cloud cover\",\"color\":\"#FFED53\",\"settings\":{},\"_hash\":0.7690144858984289,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 20) {\\n\\tvalue = 20;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":1,\"history\":{\"historyType\":2,\"timewindowMs\":60000,\"interval\":\"MONTH\",\"fixedTimewindow\":{\"startTimeMs\":1704293713163,\"endTimeMs\":1704380113163},\"quickInterval\":\"CURRENT_HALF_YEAR\"},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000},\"timezone\":null},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"showBarLabel\":true,\"barLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"12px\"},\"barLabelColor\":\"rgba(0, 0, 0, 0.54)\",\"showBarValue\":true,\"barValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"700\",\"lineHeight\":\"12px\"},\"barValueColor\":\"rgba(0, 0, 0, 0.76)\",\"showLegend\":true,\"legendPosition\":\"top\",\"legendLabelFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"legendLabelColor\":\"rgba(0, 0, 0, 0.76)\",\"showTooltip\":true,\"tooltipValueFont\":{\"family\":\"Roboto\",\"size\":12,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"16px\"},\"tooltipValueColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipShowDate\":true,\"tooltipDateFormat\":{\"format\":null,\"lastUpdateAgo\":false,\"custom\":false,\"auto\":true,\"autoDateFormatSettings\":{}},\"tooltipDateFont\":{\"family\":\"Roboto\",\"size\":11,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"400\",\"lineHeight\":\"16px\"},\"tooltipDateColor\":\"rgba(0, 0, 0, 0.76)\",\"tooltipBackgroundColor\":\"rgba(255, 255, 255, 0.76)\",\"tooltipBackgroundBlur\":4,\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"tooltipDateInterval\":true},\"title\":\"Bar chart with labels\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"public\",\"iconColor\":\"#1F6BDD\",\"useDashboardTimewindow\":false,\"displayTimewindow\":true,\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"units\":\"%\",\"decimals\":0,\"noDataDisplayMessage\":\"\",\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":null,\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true},\"margin\":\"0px\",\"borderRadius\":\"0px\",\"iconSize\":\"24px\"}"
},
"tags": [
"bar chart",

2
application/src/main/data/json/system/widget_types/line_chart.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/point_chart.json

File diff suppressed because one or more lines are too long

5
application/src/main/data/json/system/widget_types/range_chart.json

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

@ -195,7 +195,8 @@
{{ 'tooltip.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-date-format-select fxFlex excludeLastUpdateAgo includeAuto
formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-font-settings formControlName="tooltipDateFont"
[previewText]="tooltipDatePreviewFn">
</tb-font-settings>

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

@ -169,7 +169,8 @@
{{ 'tooltip.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-date-format-select fxFlex excludeLastUpdateAgo includeAuto
formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-font-settings formControlName="tooltipDateFont"
[previewText]="tooltipDatePreviewFn">
</tb-font-settings>

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

@ -25,10 +25,6 @@
forceSingleDatasource
formControlName="datasources">
</tb-datasources>
<tb-time-series-chart-y-axes-panel
formControlName="yAxes"
(axisRemoved)="yAxisRemoved($event)">
</tb-time-series-chart-y-axes-panel>
<tb-data-keys-panel
panelTitle="{{ 'widgets.chart.series' | translate }}"
addKeyTitle="{{ 'widgets.chart.add-series' | translate }}"
@ -45,6 +41,10 @@
[entityAliasId]="datasource?.entityAliasId"
formControlName="series">
</tb-data-keys-panel>
<tb-time-series-chart-y-axes-panel
formControlName="yAxes"
(axisRemoved)="yAxisRemoved($event)">
</tb-time-series-chart-y-axes-panel>
<tb-time-series-chart-thresholds-panel
formControlName="thresholds"
[aliasController]="aliasController"
@ -189,7 +189,8 @@
{{ 'tooltip.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-date-format-select fxFlex excludeLastUpdateAgo includeAuto
formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-font-settings formControlName="tooltipDateFont"
[previewText]="tooltipDatePreviewFn">
</tb-font-settings>

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

@ -37,6 +37,7 @@ import {
import { WidgetContext } from '@home/models/widget-component.models';
import { Observable } from 'rxjs';
import {
autoDateFormat,
backgroundStyle,
ComponentStyle,
DateFormatProcessor,
@ -193,7 +194,7 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
}
},
tooltipDateInterval: false,
tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss')
tooltipDateFormat: autoDateFormat()
};
this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, true);

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

@ -30,6 +30,7 @@ import {
import { WidgetContext } from '@home/models/widget-component.models';
import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils';
import {
autoDateFormat,
backgroundStyle,
ColorProcessor,
ComponentStyle,
@ -174,7 +175,7 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD
}
},
tooltipDateInterval: false,
tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss')
tooltipDateFormat: autoDateFormat()
};
this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, false);

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

@ -57,7 +57,7 @@ import {
echartsTooltipFormatter,
toNamedData
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { IntervalMath } from '@shared/models/time/time.models';
import { AggregationType, IntervalMath } from '@shared/models/time/time.models';
type BarChartDataItem = EChartsSeriesItem;
@ -97,6 +97,10 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
legendLabelStyle: ComponentStyle;
disabledLegendLabelStyle: ComponentStyle;
private get noAggregation(): boolean {
return this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE;
}
private shapeResize$: ResizeObserver;
private decimals = 0;
@ -388,7 +392,8 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
if (this.settings.showTooltip) {
const focusedSeriesIndex = this.focusedSeriesIndex();
return echartsTooltipFormatter(this.renderer, this.tooltipDateFormat,
this.settings, params, this.decimals, this.units, focusedSeriesIndex);
this.settings, params, this.decimals, this.units, focusedSeriesIndex, null,
this.noAggregation ? null : this.ctx.timeWindow.interval);
} else {
return undefined;
}

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

@ -44,9 +44,10 @@ import {
} from 'echarts/charts';
import { LabelLayout } from 'echarts/features';
import { CanvasRenderer, SVGRenderer } from 'echarts/renderers';
import { DataEntry, DataKey, DataSet, LegendDirection } from '@shared/models/widget.models';
import { DataEntry, DataKey, DataSet } from '@shared/models/widget.models';
import {
calculateAggIntervalWithWidgetTimeWindow,
Interval,
IntervalMath,
WidgetTimewindow
} from '@shared/models/time/time.models';
@ -333,7 +334,8 @@ export const echartsTooltipFormatter = (renderer: Renderer2,
decimals: number,
units: string,
focusedSeriesIndex: number,
series?: EChartsSeriesItem[]): null | HTMLElement => {
series?: EChartsSeriesItem[],
interval?: Interval): null | HTMLElement => {
if (!params || Array.isArray(params) && !params[0]) {
return null;
}
@ -349,8 +351,8 @@ export const echartsTooltipFormatter = (renderer: Renderer2,
const startTs = firstParam.value[2];
const endTs = firstParam.value[3];
if (settings.tooltipDateInterval && startTs && endTs && (endTs - 1) > startTs) {
const startDateText = tooltipDateFormat.update(startTs);
const endDateText = tooltipDateFormat.update(endTs - 1);
const startDateText = tooltipDateFormat.update(startTs, interval);
const endDateText = tooltipDateFormat.update(endTs - 1, interval);
if (startDateText === endDateText) {
dateText = startDateText;
} else {
@ -358,7 +360,7 @@ export const echartsTooltipFormatter = (renderer: Renderer2,
}
} else {
const ts = firstParam.value[0];
dateText = tooltipDateFormat.update(ts);
dateText = tooltipDateFormat.update(ts, interval);
}
renderer.appendChild(dateElement, renderer.createText(dateText));
renderer.setStyle(dateElement, 'font-family', settings.tooltipDateFont.family);

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

@ -54,6 +54,7 @@ import {
toNamedData
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { CallbackDataParams } from 'echarts/types/dist/shared';
import { AggregationType } from '@shared/models/time/time.models';
interface VisualPiece {
lt?: number;
@ -197,6 +198,10 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
disabledLegendLabelStyle: ComponentStyle;
visibleRangeItems: RangeItem[];
private get noAggregation(): boolean {
return this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE;
}
private rangeItems: RangeItem[];
private shapeResize$: ResizeObserver;
@ -324,7 +329,8 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
},
formatter: (params: CallbackDataParams[]) =>
this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat,
this.settings, params, this.decimals, this.units, 0) : undefined,
this.settings, params, this.decimals, this.units, 0, null,
this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined,
padding: [8, 12],
backgroundColor: this.settings.tooltipBackgroundColor,
borderWidth: 0,

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

@ -19,10 +19,16 @@ import {
EChartsOption,
EChartsSeriesItem,
EChartsTooltipTrigger,
EChartsTooltipWidgetSettings,
EChartsTooltipWidgetSettings, getYAxis,
measureThresholdLabelOffset
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { ComponentStyle, Font, simpleDateFormat, textStyle } from '@shared/models/widget-settings.models';
import {
autoDateFormat, AutoDateFormatSettings,
ComponentStyle,
Font,
simpleDateFormat,
textStyle, tsToFormatTimeUnit
} from '@shared/models/widget-settings.models';
import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared';
import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
import {
@ -50,6 +56,7 @@ 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';
export enum TimeSeriesChartType {
default = 'default',
@ -297,6 +304,20 @@ export interface TimeSeriesChartAxisSettings {
splitLinesColor: string;
}
export interface TimeSeriesChartXAxisSettings extends TimeSeriesChartAxisSettings {
ticksFormat: AutoDateFormatSettings;
}
export const defaultXAxisTicksFormat: AutoDateFormatSettings = {
millisecond: 'HH:mm:ss SSS',
second: 'HH:mm:ss',
minute: 'HH:mm',
hour: 'HH:mm',
day: 'MMM dd',
month: 'MMM',
year: 'yyyy'
};
export type TimeSeriesChartYAxisId = 'default' | string;
export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSettings {
@ -304,6 +325,8 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting
order?: number;
units?: string;
decimals?: number;
interval?: number;
splitNumber?: number;
min?: number | string;
max?: number | string;
intervalCalculator?: string;
@ -536,7 +559,7 @@ export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
dataZoom: boolean;
stack: boolean;
yAxes: TimeSeriesChartYAxes;
xAxis: TimeSeriesChartAxisSettings;
xAxis: TimeSeriesChartXAxisSettings;
animation: TimeSeriesChartAnimationSettings;
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
}
@ -574,6 +597,7 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
lineHeight: '1'
},
tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light,
ticksFormat: {},
showTicks: true,
ticksColor: timeSeriesChartColorScheme['axis.ticks'].light,
showLine: true,
@ -616,7 +640,7 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
},
tooltipValueColor: 'rgba(0, 0, 0, 0.76)',
tooltipShowDate: true,
tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss'),
tooltipDateFormat: autoDateFormat(),
tooltipDateFont: {
family: 'Roboto',
size: 11,
@ -851,12 +875,59 @@ export const createTimeSeriesYAxis = (units: string,
return yAxis;
};
export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartAxisSettings,
min: number, max: number, darkMode: boolean): XAXisOption => {
export const updateYAxisIntervals = (chart: ECharts,
yAxis: TimeSeriesChartYAxis, empty: boolean): boolean => {
let changed = false;
let interval: number;
let splitNumber: number;
let minInterval: number;
if (!empty) {
interval = calculateYAxisInterval(chart, yAxis);
if (isUndefinedOrNull(interval)) {
if (isDefinedAndNotNull(yAxis.settings.splitNumber)) {
splitNumber = yAxis.settings.splitNumber;
} else {
minInterval = (1 / Math.pow(10, yAxis.decimals));
}
}
}
if (yAxis.option.interval !== interval) {
yAxis.option.interval = interval;
changed = true;
}
if (yAxis.option.splitNumber !== splitNumber) {
yAxis.option.splitNumber = splitNumber;
changed = true;
}
if (yAxis.option.minInterval !== minInterval) {
yAxis.option.minInterval = minInterval;
changed = true;
}
return changed;
};
const calculateYAxisInterval = (chart: ECharts, yAxis: TimeSeriesChartYAxis): number | undefined => {
let interval = yAxis.settings.interval;
if (yAxis.intervalCalculator) {
const axis = getYAxis(chart, yAxis.id);
if (axis) {
try {
interval = yAxis.intervalCalculator(axis);
} catch (_e) {}
}
}
return interval;
};
export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettings,
min: number, max: number,
datePipe: DatePipe,
darkMode: boolean): XAXisOption => {
const xAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont,
settings.tickLabelColor, darkMode, 'axis.tickLabel');
const xAxisNameStyle = createChartTextStyle(settings.labelFont,
settings.labelColor, darkMode, 'axis.label');
const ticksFormat = mergeDeep({}, defaultXAxisTicksFormat, settings.ticksFormat);
return {
show: settings.show,
type: 'time',
@ -885,15 +956,16 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartAxisSetting
fontFamily: xAxisTickLabelStyle.fontFamily,
fontSize: xAxisTickLabelStyle.fontSize,
hideOverlap: true,
/* formatter: {
year: '{yyyy}',
month: '{MMM}',
day: '{d}',
hour: '{HH}:{mm}',
minute: '{HH}:{mm}',
second: '{HH}:{mm}:{ss}',
millisecond: '{hh}:{mm}:{ss} {SSS}'
} */
formatter: (value: number, index: number, extra: {level: number}) => {
const unit = tsToFormatTimeUnit(value);
const format = ticksFormat[unit];
const formatted = datePipe.transform(value, format);
if (extra.level > 0) {
return `{primary|${formatted}}`;
} else {
return formatted;
}
}
},
axisLine: {
show: settings.showLine,
@ -970,10 +1042,10 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[],
dataGroupId: item.id,
yAxisIndex: item.yAxisIndex,
data: [],
tooltip: {
show: false
},
markLine: {
tooltip: {
show: false
},
lineStyle: {
width: item.settings.lineWidth,
color: prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'),

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

@ -39,7 +39,7 @@ import {
TimeSeriesChartYAxis,
TimeSeriesChartYAxisId,
TimeSeriesChartYAxisSettings,
updateDarkMode
updateDarkMode, updateYAxisIntervals
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { ResizeObserver } from '@juggle/resize-observer';
import {
@ -51,12 +51,11 @@ import {
echartsTooltipFormatter,
EChartsTooltipTrigger,
getAxisExtent,
getYAxis,
measureXAxisNameHeight,
measureYAxisNameWidth,
toNamedData
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { DateFormatProcessor } from '@shared/models/widget-settings.models';
import { autoDateFormat, DateFormatProcessor } from '@shared/models/widget-settings.models';
import { isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils';
import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models';
import * as echarts from 'echarts/core';
@ -485,7 +484,8 @@ export class TbTimeSeriesChart {
},
formatter: (params: CallbackDataParams[]) =>
this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat,
this.settings, params, 0, '', -1, this.dataItems) : undefined,
this.settings, params, 0, '', -1, this.dataItems,
this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined,
padding: [8, 12],
backgroundColor: this.settings.tooltipBackgroundColor,
borderWidth: 0,
@ -499,7 +499,7 @@ export class TbTimeSeriesChart {
}],
xAxis: [
createTimeSeriesXAxisOption(this.settings.xAxis, this.ctx.defaultSubscription.timeWindow.minTime,
this.ctx.defaultSubscription.timeWindow.maxTime, this.darkMode)
this.ctx.defaultSubscription.timeWindow.maxTime, this.ctx.date, this.darkMode)
],
yAxis: this.yAxisList.map(axis => axis.option),
dataZoom: [
@ -611,7 +611,7 @@ export class TbTimeSeriesChart {
this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis', 'xAxis', 'grid'], lazyUpdate: true});
}
changed = this.calculateYAxisInterval(this.yAxisList);
changed = this.updateYAxesIntervals(this.yAxisList);
if (changed) {
this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis'], lazyUpdate: true});
@ -696,26 +696,12 @@ export class TbTimeSeriesChart {
return !axisDataItems.length;
}
private calculateYAxisInterval(axisList: TimeSeriesChartYAxis[]): boolean {
private updateYAxesIntervals(axisList: TimeSeriesChartYAxis[]): boolean {
let changed = false;
for (const yAxis of axisList) {
const minInterval = this.yAxisEmpty(yAxis) ? undefined : (1 / Math.pow(10, yAxis.decimals));
if (yAxis.option.minInterval !== minInterval) {
yAxis.option.minInterval = minInterval;
if (updateYAxisIntervals(this.timeSeriesChart, yAxis, this.yAxisEmpty(yAxis))) {
changed = true;
}
if (yAxis.intervalCalculator) {
const axis = getYAxis(this.timeSeriesChart, yAxis.id);
if (axis) {
try {
const interval = yAxis.intervalCalculator(axis);
if ((yAxis.option as any).interval !== interval) {
(yAxis.option as any).interval = interval;
changed = true;
}
} catch (_e) {}
}
}
}
return changed;
}

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

@ -114,7 +114,8 @@
{{ 'tooltip.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-date-format-select fxFlex excludeLastUpdateAgo includeAuto
formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-font-settings formControlName="tooltipDateFont"
[previewText]="tooltipDatePreviewFn">
</tb-font-settings>

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

@ -104,7 +104,8 @@
{{ 'tooltip.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-date-format-select fxFlex excludeLastUpdateAgo includeAuto
formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-font-settings formControlName="tooltipDateFont"
[previewText]="tooltipDatePreviewFn">
</tb-font-settings>

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

@ -123,7 +123,8 @@
{{ 'tooltip.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-date-format-select fxFlex excludeLastUpdateAgo includeAuto
formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-font-settings formControlName="tooltipDateFont"
[previewText]="tooltipDatePreviewFn">
</tb-font-settings>

49
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.html

@ -0,0 +1,49 @@
<!--
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.
-->
<div class="tb-auto-date-format-settings-panel" [formGroup]="autoDateFormatFormGroup">
<div class="tb-auto-date-format-settings-title" translate>date.time-granularity-formats</div>
<div class="tb-auto-date-format-settings-panel-content">
<div class="tb-form-row" *ngFor="let unit of formatTimeUnits">
<div class="fixed-title-width">{{ formatTimeUnitTranslations.get(unit) | translate }}</div>
<div fxFlex fxLayout="column" fxLayoutGap="8px"
fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutGap.gt-xs="16px">
<mat-form-field class="tb-date-format-input flex" appearance="outline" subscriptSizing="dynamic">
<input required matInput [formControlName]="unit" placeholder="{{ 'widget-config.set' | translate }}">
<div matIconSuffix tb-help-popup="date/date-format" [tb-help-popup-style]="{width: '800px'}"></div>
</mat-form-field>
<mat-divider fxHide fxShow.gt-xs vertical></mat-divider>
<div class="preview-text" fxFlex>{{ previewText[unit] }}</div>
</div>
</div>
</div>
<div class="tb-auto-date-format-settings-panel-buttons">
<button mat-button
color="primary"
type="button"
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button
color="primary"
type="button"
(click)="applyAutoDateFormatSettings()"
[disabled]="autoDateFormatFormGroup.invalid || !autoDateFormatFormGroup.dirty">
{{ 'action.apply' | translate }}
</button>
</div>
</div>

73
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.scss

@ -0,0 +1,73 @@
/**
* 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 '../../../../../../../../scss/constants';
.tb-auto-date-format-settings-panel {
width: 620px;
display: flex;
flex-direction: column;
gap: 16px;
@media #{$mat-lt-md} {
width: 90vw;
}
.tb-auto-date-format-settings-title {
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.87);
}
.tb-auto-date-format-settings-panel-content {
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
margin: -10px;
padding: 10px;
}
.tb-form-row {
.fixed-title-width {
min-width: 120px;
}
.mat-mdc-form-field.tb-date-format-input {
.mat-mdc-text-field-wrapper.mdc-text-field--outlined {
.mat-mdc-form-field-flex {
.mat-mdc-form-field-icon-suffix {
display: flex;
align-items: center;
line-height: normal;
}
}
}
}
.preview-text {
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.38);
}
}
.tb-auto-date-format-settings-panel-buttons {
height: 40px;
display: flex;
flex-direction: row;
gap: 16px;
justify-content: flex-end;
align-items: flex-end;
}
}

92
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings-panel.component.ts

@ -0,0 +1,92 @@
///
/// 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, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import {
AutoDateFormatSettings, defaultAutoDateFormatSettings,
FormatTimeUnit,
formatTimeUnits,
formatTimeUnitTranslations
} from '@shared/models/widget-settings.models';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { DatePipe } from '@angular/common';
@Component({
selector: 'tb-auto-date-format-settings-panel',
templateUrl: './auto-date-format-settings-panel.component.html',
providers: [],
styleUrls: ['./auto-date-format-settings-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class AutoDateFormatSettingsPanelComponent extends PageComponent implements OnInit {
formatTimeUnits = formatTimeUnits;
formatTimeUnitTranslations = formatTimeUnitTranslations;
@Input()
autoDateFormatSettings: AutoDateFormatSettings;
@Input()
defaultValues = defaultAutoDateFormatSettings;
@Input()
popover: TbPopoverComponent<AutoDateFormatSettingsPanelComponent>;
@Output()
autoDateFormatSettingsApplied = new EventEmitter<AutoDateFormatSettings>();
autoDateFormatFormGroup: UntypedFormGroup;
previewText: {[unit in FormatTimeUnit]: string} = {} as any;
constructor(private date: DatePipe,
private fb: UntypedFormBuilder,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.autoDateFormatFormGroup = this.fb.group({});
for (const unit of formatTimeUnits) {
this.autoDateFormatFormGroup.addControl(unit,
this.fb.control(this.autoDateFormatSettings[unit] || this.defaultValues[unit], [Validators.required]));
this.autoDateFormatFormGroup.get(unit).valueChanges.subscribe((value: string) => {
this.previewText[unit] = this.date.transform(Date.now(), value);
});
this.previewText[unit] = this.date.transform(Date.now(), this.autoDateFormatSettings[unit] || this.defaultValues[unit]);
}
}
cancel() {
this.popover?.hide();
}
applyAutoDateFormatSettings() {
const autoDateFormatSettings: AutoDateFormatSettings = this.autoDateFormatFormGroup.value;
for (const unit of formatTimeUnits) {
if (autoDateFormatSettings[unit] === this.defaultValues[unit]) {
delete autoDateFormatSettings[unit];
}
}
this.autoDateFormatSettingsApplied.emit(autoDateFormatSettings);
}
}

25
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.html

@ -0,0 +1,25 @@
<!--
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.
-->
<button type="button"
mat-stroked-button
class="tb-box-button"
[disabled]="disabled"
#matButton
(click)="openAutoFormatSettingsPopup($event, matButton)">
<tb-icon matButtonIcon>mdi:clock-edit-outline</tb-icon>
</button>

99
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.ts

@ -0,0 +1,99 @@
///
/// 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, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { AutoDateFormatSettings, defaultAutoDateFormatSettings } from '@shared/models/widget-settings.models';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { deepClone, mergeDeep } from '@core/utils';
import {
AutoDateFormatSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/auto-date-format-settings-panel.component';
@Component({
selector: 'tb-auto-date-format-settings',
templateUrl: './auto-date-format-settings.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AutoDateFormatSettingsComponent),
multi: true
}
]
})
export class AutoDateFormatSettingsComponent implements OnInit, ControlValueAccessor {
@Input()
disabled: boolean;
@Input()
defaultValues = defaultAutoDateFormatSettings;
private modelValue: AutoDateFormatSettings;
private propagateChange = null;
constructor(private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef) {}
ngOnInit(): void {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
writeValue(value: AutoDateFormatSettings): void {
this.modelValue = value;
}
openAutoFormatSettingsPopup($event: Event, matButton: MatButton) {
if ($event) {
$event.stopPropagation();
}
const trigger = matButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const ctx: any = {
autoDateFormatSettings: deepClone(this.modelValue),
defaultValues: this.defaultValues
};
const autoDateFormatSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, AutoDateFormatSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
ctx,
{},
{}, {}, true);
autoDateFormatSettingsPanelPopover.tbComponentRef.instance.popover = autoDateFormatSettingsPanelPopover;
autoDateFormatSettingsPanelPopover.tbComponentRef.instance.autoDateFormatSettingsApplied.subscribe((autoDateFormatSettings) => {
autoDateFormatSettingsPanelPopover.hide();
this.modelValue = autoDateFormatSettings;
this.propagateChange(this.modelValue);
});
}
}
}

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

@ -74,6 +74,10 @@
<div translate>widgets.time-series-chart.axis.tick-labels</div>
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-auto-date-format-settings *ngIf="axisType === 'xAxis'"
formControlName="ticksFormat"
[defaultValues]="defaultXAxisTicksFormat">
</tb-auto-date-format-settings>
<tb-font-settings formControlName="tickLabelFont"
clearButton
disabledLineHeight
@ -126,6 +130,24 @@
formControlName="splitLinesColor">
</tb-color-input>
</div>
<div *ngIf="axisType === 'yAxis'" class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'widgets.time-series-chart.axis.ticks-interval-hint' | translate }}">
{{ 'widgets.time-series-chart.axis.ticks-interval' | translate }}
</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 }}">
</mat-form-field>
</div>
<div *ngIf="axisType === 'yAxis'" class="tb-form-row space-between">
<div tb-hint-tooltip-icon="{{ 'widgets.time-series-chart.axis.split-number-hint' | translate }}">
{{ 'widgets.time-series-chart.axis.split-number' | translate }}
</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 }}">
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
<div *ngIf="axisType === 'yAxis'" class="tb-form-panel no-border no-padding-top">

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

@ -23,9 +23,9 @@ import {
Validators
} from '@angular/forms';
import {
AxisPosition,
AxisPosition, defaultXAxisTicksFormat,
timeSeriesAxisPositionTranslations,
TimeSeriesChartAxisSettings,
TimeSeriesChartAxisSettings, TimeSeriesChartXAxisSettings,
TimeSeriesChartYAxisSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { merge } from 'rxjs';
@ -58,6 +58,8 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
defaultXAxisTicksFormat = defaultXAxisTicksFormat;
@Input()
disabled: boolean;
@ -68,7 +70,7 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
@coerceBoolean()
advanced = false;
private modelValue: TimeSeriesChartAxisSettings | TimeSeriesChartYAxisSettings;
private modelValue: TimeSeriesChartXAxisSettings | TimeSeriesChartYAxisSettings;
private propagateChange = null;
@ -103,8 +105,12 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
this.axisSettingsFormGroup.addControl('units', this.fb.control(null, []));
this.axisSettingsFormGroup.addControl('decimals', this.fb.control(null, [Validators.min(0)]));
this.axisSettingsFormGroup.addControl('ticksFormatter', this.fb.control(null, []));
this.axisSettingsFormGroup.addControl('interval', this.fb.control(null, [Validators.min(0)]));
this.axisSettingsFormGroup.addControl('splitNumber', this.fb.control(null, [Validators.min(1)]));
this.axisSettingsFormGroup.addControl('min', this.fb.control(null, []));
this.axisSettingsFormGroup.addControl('max', this.fb.control(null, []));
} else if (this.axisType === 'xAxis') {
this.axisSettingsFormGroup.addControl('ticksFormat', this.fb.control(null, []));
}
this.axisSettingsFormGroup.valueChanges.subscribe(() => {
this.updateModel();
@ -161,12 +167,18 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
if (this.axisType === 'yAxis') {
this.axisSettingsFormGroup.get('ticksFormatter').enable({emitEvent: false});
}
if (this.axisType === 'xAxis') {
this.axisSettingsFormGroup.get('ticksFormat').enable({emitEvent: false});
}
} else {
this.axisSettingsFormGroup.get('tickLabelFont').disable({emitEvent: false});
this.axisSettingsFormGroup.get('tickLabelColor').disable({emitEvent: false});
if (this.axisType === 'yAxis') {
this.axisSettingsFormGroup.get('ticksFormatter').disable({emitEvent: false});
}
if (this.axisType === 'xAxis') {
this.axisSettingsFormGroup.get('ticksFormat').disable({emitEvent: false});
}
}
if (showTicks) {
this.axisSettingsFormGroup.get('ticksColor').enable({emitEvent: false});

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.html

@ -23,4 +23,9 @@
*ngIf="dateFormatFormControl.value?.custom" matSuffix mat-icon-button (click)="openDateFormatSettingsPopup($event, customFormatButton)">
<tb-icon>edit</tb-icon>
</button>
<button #autoFormatButton
*ngIf="dateFormatFormControl.value?.auto && !dateFormatFormControl.value?.custom"
matSuffix mat-icon-button (click)="openAutoFormatSettingsPopup($event, autoFormatButton)">
<tb-icon>edit</tb-icon>
</button>
</mat-form-field>

49
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts

@ -16,16 +16,26 @@
import { Component, forwardRef, Input, OnInit, Renderer2, ViewChild, ViewContainerRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms';
import { compareDateFormats, dateFormats, DateFormatSettings } from '@shared/models/widget-settings.models';
import {
AutoDateFormatSettings,
compareDateFormats,
dateFormats,
DateFormatSettings,
dateFormatsWithAuto,
defaultAutoDateFormatSettings
} from '@shared/models/widget-settings.models';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { deepClone } from '@core/utils';
import { deepClone, mergeDeep } from '@core/utils';
import {
DateFormatSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/date-format-settings-panel.component';
import { coerceBoolean } from '@shared/decorators/coercion';
import {
AutoDateFormatSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/auto-date-format-settings-panel.component';
@Component({
selector: 'tb-date-format-select',
@ -51,6 +61,10 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor {
@coerceBoolean()
excludeLastUpdateAgo = false;
@Input()
@coerceBoolean()
includeAuto = false;
dateFormatList: DateFormatSettings[];
dateFormatsCompare = compareDateFormats;
@ -70,8 +84,9 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor {
private viewContainerRef: ViewContainerRef) {}
ngOnInit(): void {
const targetDateFormats = this.includeAuto ? dateFormatsWithAuto : dateFormats;
this.dateFormatList = this.excludeLastUpdateAgo ?
dateFormats.filter(format => !format.lastUpdateAgo) : dateFormats;
targetDateFormats.filter(format => !format.lastUpdateAgo) : dateFormats;
this.dateFormatFormControl = new UntypedFormControl();
this.dateFormatFormControl.valueChanges.subscribe((value: DateFormatSettings) => {
this.updateModel(value);
@ -116,6 +131,8 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor {
return this.translate.instant('date.custom-date');
} else if (value.lastUpdateAgo) {
return this.translate.instant('date.last-update-n-ago');
} else if (value.auto) {
return this.translate.instant('date.auto');
} else {
if (!this.formatCache[value.format]) {
this.formatCache[value.format] = this.date.transform(Date.now(), value.format);
@ -148,4 +165,30 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor {
});
}
}
openAutoFormatSettingsPopup($event: Event, matButton: MatButton) {
if ($event) {
$event.stopPropagation();
}
const trigger = matButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const ctx: any = {
autoDateFormatSettings: mergeDeep({} as AutoDateFormatSettings,
defaultAutoDateFormatSettings, this.modelValue.autoDateFormatSettings)
};
const autoDateFormatSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, AutoDateFormatSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
ctx,
{},
{}, {}, true);
autoDateFormatSettingsPanelPopover.tbComponentRef.instance.popover = autoDateFormatSettingsPanelPopover;
autoDateFormatSettingsPanelPopover.tbComponentRef.instance.autoDateFormatSettingsApplied.subscribe((autoDateFormatSettings) => {
autoDateFormatSettingsPanelPopover.hide();
this.modelValue.autoDateFormatSettings = autoDateFormatSettings;
this.propagateChange(this.modelValue);
});
}
}
}

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

@ -121,6 +121,12 @@ import {
import {
TimeSeriesChartAnimationSettingsComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component';
import {
AutoDateFormatSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/auto-date-format-settings-panel.component';
import {
AutoDateFormatSettingsComponent
} from '@home/components/widget/lib/settings/common/auto-date-format-settings.component';
@NgModule({
declarations: [
@ -134,6 +140,8 @@ import {
CssSizeInputComponent,
DateFormatSelectComponent,
DateFormatSettingsPanelComponent,
AutoDateFormatSettingsComponent,
AutoDateFormatSettingsPanelComponent,
BackgroundSettingsComponent,
BackgroundSettingsPanelComponent,
ValueSourceComponent,
@ -185,6 +193,8 @@ import {
CssSizeInputComponent,
DateFormatSelectComponent,
DateFormatSettingsPanelComponent,
AutoDateFormatSettingsComponent,
AutoDateFormatSettingsPanelComponent,
BackgroundSettingsComponent,
BackgroundSettingsPanelComponent,
ValueSourceComponent,

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

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { isDefinedAndNotNull, isNumber, isNumeric, isUndefinedOrNull, parseFunction } from '@core/utils';
import { isDefinedAndNotNull, isNumber, isNumeric, isUndefinedOrNull, mergeDeep, parseFunction } from '@core/utils';
import {
DataEntry,
DataKey,
@ -34,6 +34,8 @@ import { Observable, of } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { map } from 'rxjs/operators';
import { DomSanitizer } from '@angular/platform-browser';
import { AVG_MONTH, DAY, HOUR, Interval, IntervalMath, MINUTE, SECOND, YEAR } from '@shared/models/time/time.models';
import moment from 'moment';
export type ComponentStyle = {[klass: string]: any};
@ -323,35 +325,85 @@ class FunctionColorProcessor extends ColorProcessor {
}
}
export type FormatTimeUnit = 'millisecond' | 'second' | 'minute' | 'hour'
| 'day' | 'month' | 'year';
export const formatTimeUnits: FormatTimeUnit[] = [
'year', 'month', 'day', 'hour', 'minute', 'second', 'millisecond'
];
export const formatTimeUnitTranslations = new Map<FormatTimeUnit, string>(
[
['year', 'date.unit-year'],
['month', 'date.unit-month'],
['day', 'date.unit-day'],
['hour', 'date.unit-hour'],
['minute', 'date.unit-minute'],
['second', 'date.unit-second'],
['millisecond', 'date.unit-millisecond']
]
);
export type AutoDateFormatSettings = {
[unit in FormatTimeUnit]?: string;
};
export interface DateFormatSettings {
format?: string;
lastUpdateAgo?: boolean;
custom?: boolean;
auto?: boolean;
hideLastUpdatePrefix?: boolean;
autoDateFormatSettings?: AutoDateFormatSettings;
}
export const simpleDateFormat = (format: string): DateFormatSettings => ({
format,
lastUpdateAgo: false,
custom: false
custom: false,
auto: false
});
export const lastUpdateAgoDateFormat = (): DateFormatSettings => ({
format: null,
lastUpdateAgo: true,
custom: false
custom: false,
auto: false
});
export const customDateFormat = (format: string): DateFormatSettings => ({
format,
lastUpdateAgo: false,
custom: true
custom: true,
auto: false
});
export const defaultAutoDateFormatSettings: AutoDateFormatSettings = {
millisecond: 'MMM dd yyyy HH:mm:ss.SSS',
second: 'MMM dd yyyy HH:mm:ss',
minute: 'MMM dd yyyy HH:mm',
hour: 'MMM dd yyyy HH:mm',
day: 'MMM dd yyyy',
month: 'MMM yyyy',
year: 'yyyy'
};
export const autoDateFormat = (): DateFormatSettings => ({
format: null,
lastUpdateAgo: false,
custom: false,
auto: true,
autoDateFormatSettings: {}
});
export const dateFormats = ['MMM dd yyyy HH:mm', 'dd MMM yyyy HH:mm', 'dd MMM yyyy HH:mm:ss', 'yyyy MMM dd HH:mm',
'MM/dd/yyyy HH:mm', 'dd/MM/yyyy HH:mm', 'yyyy/MM/dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss.SSS']
export const dateFormats = ['MMM yyyy', 'MMM dd yyyy', 'MMM dd yyyy HH:mm', 'dd MMM yyyy HH:mm', 'dd MMM yyyy HH:mm:ss',
'yyyy MMM dd HH:mm', 'MM/dd/yyyy HH:mm', 'dd/MM/yyyy HH:mm', 'MMM dd yyyy HH:mm:ss', 'yyyy/MM/dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss',
'MMM dd yyyy HH:mm:ss.SSS', 'yyyy-MM-dd HH:mm:ss.SSS']
.map(f => simpleDateFormat(f)).concat([lastUpdateAgoDateFormat(), customDateFormat('EEE, MMMM dd, yyyy')]);
export const dateFormatsWithAuto = [autoDateFormat()].concat(dateFormats);
export const compareDateFormats = (df1: DateFormatSettings, df2: DateFormatSettings): boolean => {
if (df1 === df2) {
return true;
@ -360,7 +412,9 @@ export const compareDateFormats = (df1: DateFormatSettings, df2: DateFormatSetti
return true;
} else if (df1.custom && df2.custom) {
return true;
} else if (!df1.lastUpdateAgo && !df2.lastUpdateAgo && !df1.custom && !df2.custom) {
} else if (df1.auto && df2.auto && !df1.custom && !df2.custom) {
return true;
} else if (!df1.lastUpdateAgo && !df2.lastUpdateAgo && !df1.custom && !df2.custom && !df1.auto && !df2.auto) {
return df1.format === df2.format;
}
}
@ -372,6 +426,8 @@ export abstract class DateFormatProcessor {
static fromSettings($injector: Injector, settings: DateFormatSettings): DateFormatProcessor {
if (settings.lastUpdateAgo) {
return new LastUpdateAgoDateFormatProcessor($injector, settings);
} else if (settings.auto && !settings.custom) {
return new AutoDateFormatProcessor($injector, settings);
} else {
return new SimpleDateFormatProcessor($injector, settings);
}
@ -383,7 +439,7 @@ export abstract class DateFormatProcessor {
protected settings: DateFormatSettings) {
}
abstract update(ts: string | number | Date): string;
abstract update(ts: string | number | Date, interval?: Interval): string;
}
@ -437,6 +493,91 @@ export class LastUpdateAgoDateFormatProcessor extends DateFormatProcessor {
}
export class AutoDateFormatProcessor extends DateFormatProcessor {
private datePipe: DatePipe;
private readonly autoDateFormatSettings: AutoDateFormatSettings;
constructor(protected $injector: Injector,
protected settings: DateFormatSettings) {
super($injector, settings);
this.datePipe = $injector.get(DatePipe);
this.autoDateFormatSettings = mergeDeep({} as AutoDateFormatSettings,
defaultAutoDateFormatSettings, this.settings.autoDateFormatSettings);
}
update(ts: string| number | Date, interval?: Interval): string {
if (ts) {
const unit = interval ? intervalToFormatTimeUnit(interval) : tsToFormatTimeUnit(ts);
const format = this.autoDateFormatSettings[unit];
if (format) {
this.formatted = this.datePipe.transform(ts, format);
} else {
this.formatted = '&nbsp;';
}
} else {
this.formatted = '&nbsp;';
}
return this.formatted;
}
}
const intervalToFormatTimeUnit = (interval: Interval): FormatTimeUnit => {
const intervalValue = IntervalMath.numberValue(interval);
if (intervalValue < SECOND) {
return 'millisecond';
} else if (intervalValue < MINUTE) {
return 'second';
} else if (intervalValue < HOUR) {
return 'minute';
} else if (intervalValue < DAY) {
return 'hour';
} else if (intervalValue < AVG_MONTH) {
return 'day';
} else if (intervalValue < YEAR) {
return 'month';
} else {
return 'year';
}
};
export const tsToFormatTimeUnit = (ts: string | number | Date): FormatTimeUnit => {
const date = moment(ts);
const M = date.month() + 1;
const d = date.date();
const h = date.hours();
const m = date.minutes();
const s = date.seconds();
const S = date.milliseconds();
const isSecond = S === 0;
const isMinute = isSecond && s === 0;
const isHour = isMinute && m === 0;
const isDay = isHour && h === 0;
const isMonth = isDay && d === 1;
const isYear = isMonth && M === 1;
if (isYear) {
return 'year';
}
else if (isMonth) {
return 'month';
}
else if (isDay) {
return 'day';
}
else if (isHour) {
return 'hour';
}
else if (isMinute) {
return 'minute';
}
else if (isSecond) {
return 'second';
}
else {
return 'millisecond';
}
};
export enum BackgroundType {
image = 'image',
color = 'color'

14
ui-ngx/src/assets/dashboard/sys_admin_home_page.json

@ -2023,6 +2023,7 @@
"lineHeight": "1"
},
"tickLabelColor": "rgba(0, 0, 0, 0.54)",
"ticksFormat": {},
"showTicks": false,
"ticksColor": "rgba(0, 0, 0, 0.54)",
"showLine": false,
@ -2076,9 +2077,11 @@
"tooltipValueColor": "rgba(0, 0, 0, 0.76)",
"tooltipShowDate": true,
"tooltipDateFormat": {
"format": "MMMM dd, yyyy",
"format": null,
"lastUpdateAgo": false,
"custom": true
"custom": false,
"auto": true,
"autoDateFormatSettings": {}
},
"tooltipDateFont": {
"family": "Roboto",
@ -2494,6 +2497,7 @@
"lineHeight": "1"
},
"tickLabelColor": "rgba(0, 0, 0, 0.54)",
"ticksFormat": {},
"showTicks": false,
"ticksColor": "rgba(0, 0, 0, 0.54)",
"showLine": false,
@ -2547,9 +2551,11 @@
"tooltipValueColor": "rgba(0, 0, 0, 0.76)",
"tooltipShowDate": true,
"tooltipDateFormat": {
"format": "dd MMM yyyy HH:mm:ss",
"format": null,
"lastUpdateAgo": false,
"custom": false
"custom": false,
"auto": true,
"autoDateFormatSettings": {}
},
"tooltipDateFont": {
"family": "Roboto",

14
ui-ngx/src/assets/dashboard/tenant_admin_home_page.json

@ -671,6 +671,7 @@
"lineHeight": "1"
},
"tickLabelColor": "rgba(0, 0, 0, 0.54)",
"ticksFormat": {},
"showTicks": false,
"ticksColor": "rgba(0, 0, 0, 0.54)",
"showLine": false,
@ -724,9 +725,11 @@
"tooltipValueColor": "rgba(0, 0, 0, 0.76)",
"tooltipShowDate": true,
"tooltipDateFormat": {
"format": "MMMM dd, yyyy",
"format": null,
"lastUpdateAgo": false,
"custom": true
"custom": false,
"auto": true,
"autoDateFormatSettings": {}
},
"tooltipDateFont": {
"family": "Roboto",
@ -997,6 +1000,7 @@
"lineHeight": "1"
},
"tickLabelColor": "rgba(0, 0, 0, 0.54)",
"ticksFormat": {},
"showTicks": false,
"ticksColor": "rgba(0, 0, 0, 0.54)",
"showLine": false,
@ -1050,9 +1054,11 @@
"tooltipValueColor": "rgba(0, 0, 0, 0.76)",
"tooltipShowDate": true,
"tooltipDateFormat": {
"format": "MMMM dd, yyyy",
"format": null,
"lastUpdateAgo": false,
"custom": true
"custom": false,
"auto": true,
"autoDateFormatSettings": {}
},
"tooltipDateFont": {
"family": "Roboto",

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

@ -1025,7 +1025,16 @@
"last-update-n-ago-text": "Last update {{ agoText }}",
"custom-date": "Custom date",
"format": "Format",
"preview": "Preview"
"preview": "Preview",
"auto": "Auto",
"time-granularity-formats": "Time granularity formats",
"unit-year": "Years",
"unit-month": "Months",
"unit-day": "Days",
"unit-hour": "Hours",
"unit-minute": "Minutes",
"unit-second": "Seconds",
"unit-millisecond": "Milliseconds"
},
"datetime": {
"date-from": "Date from",
@ -6691,6 +6700,10 @@
"show-split-lines": "Show split lines",
"show-split-lines-x-axis-hint": "If enabled, the vertical lines on the chart will be shown.",
"show-split-lines-y-axis-hint": "If enabled, the horizontal lines on the chart will be shown.",
"ticks-interval": "Ticks interval",
"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",

Loading…
Cancel
Save