diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json
index 14c09d3fc4..4a3240fdd2 100644
--- a/application/src/main/data/json/system/widget_bundles/charts.json
+++ b/application/src/main/data/json/system/widget_bundles/charts.json
@@ -2,10 +2,9 @@
"widgetsBundle": {
"alias": "charts",
"title": "Charts",
- "image": "data:image/png;base64,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",
+ "image": "tb-image:Y2hhcnRzX3N5c3RlbV9idW5kbGVfaW1hZ2UucG5n:IkNoYXJ0cyIgc3lzdGVtIGJ1bmRsZSBpbWFnZQ==;data:image/png;base64,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",
"description": "Display time series data using customizable line and bar charts. Use various pie charts to display the latest values.",
"order": 1000,
- "externalId": null,
"name": "Charts"
},
"widgetTypeFqns": [
@@ -13,6 +12,7 @@
"charts.state_chart",
"range_chart",
"charts.timeseries_bars_flot",
+ "bar_chart_with_labels",
"cards.aggregated_value_card",
"charts.bars",
"charts.pie",
diff --git a/application/src/main/data/json/system/widget_types/bar_chart_with_labels.json b/application/src/main/data/json/system/widget_types/bar_chart_with_labels.json
new file mode 100644
index 0000000000..6120c4f1c5
--- /dev/null
+++ b/application/src/main/data/json/system/widget_types/bar_chart_with_labels.json
@@ -0,0 +1,30 @@
+{
+ "fqn": "bar_chart_with_labels",
+ "name": "Bar chart with labels",
+ "deprecated": false,
+ "image": "tb-image:cmFuZ2VfY2hhcnRfc3lzdGVtX3dpZGdldF9pbWFnZS5wbmc=:IlJhbmdlIGNoYXJ0IiBzeXN0ZW0gd2lkZ2V0IGltYWdl;data:image/png;base64,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",
+ "description": "Displays changes to time-series data over time visualized with color ranges — for example, temperature or humidity readings.",
+ "descriptor": {
+ "type": "timeseries",
+ "sizeX": 8,
+ "sizeY": 5,
+ "resources": [],
+ "templateHtml": "\n",
+ "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
+ "controllerScript": "self.onInit = function() {\n self.ctx.$scope.barChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.barChartWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: false,\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries' }];\n }\n };\n}\n",
+ "settingsSchema": "{}",
+ "dataKeySettingsSchema": "{}",
+ "latestDataKeySettingsSchema": "{}",
+ "settingsDirective": "",
+ "dataKeySettingsDirective": "",
+ "latestDataKeySettingsDirective": "",
+ "hasBasicMode": false,
+ "basicModeDirective": "",
+ "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\":2221714000,\"fixedTimewindow\":{\"startTimeMs\":1703599035699,\"endTimeMs\":1703685435699},\"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\":{},\"title\":\"Bar chart with labels\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"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\":\"0px\"}"
+ },
+ "tags": [
+ "range",
+ "color range",
+ "line chart"
+ ]
+}
\ No newline at end of file
diff --git a/ui-ngx/src/app/core/services/time.service.ts b/ui-ngx/src/app/core/services/time.service.ts
index f9ab858b4f..421b81f8bc 100644
--- a/ui-ngx/src/app/core/services/time.service.ts
+++ b/ui-ngx/src/app/core/services/time.service.ts
@@ -38,7 +38,7 @@ export interface TimeInterval {
const MIN_INTERVAL = SECOND;
const MAX_INTERVAL = 365 * 20 * DAY;
-const MIN_LIMIT = 7;
+const MIN_LIMIT = 1;
const MAX_DATAPOINTS_LIMIT = 500;
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html
new file mode 100644
index 0000000000..b490802022
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
{{ legendItem.label }}
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss
new file mode 100644
index 0000000000..6e57a12d6c
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss
@@ -0,0 +1,105 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-bar-chart-panel {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ padding: 20px 24px 24px 24px;
+ > div:not(.tb-bar-chart-overlay) {
+ z-index: 1;
+ }
+ .tb-bar-chart-overlay {
+ position: absolute;
+ top: 12px;
+ left: 12px;
+ bottom: 12px;
+ right: 12px;
+ }
+ div.tb-widget-title {
+ padding: 0;
+ }
+ .tb-bar-chart-content {
+ flex: 1;
+ min-width: 0;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ &.legend-top {
+ flex-direction: column-reverse;
+ }
+ &.legend-right {
+ flex-direction: row;
+ }
+ &.legend-left {
+ flex-direction: row-reverse;
+ }
+ .tb-bar-chart-shape {
+ flex: 1;
+ min-width: 0;
+ min-height: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ .tb-bar-chart-legend {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ align-self: stretch;
+ flex-wrap: wrap;
+ column-gap: 24px;
+ row-gap: 8px;
+ .tb-bar-chart-legend-item {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-start;
+ user-select: none;
+ cursor: pointer;
+ .tb-bar-chart-legend-item-label {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ color: #ccc;
+ .tb-bar-chart-legend-item-label-circle {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background-color: #ccc;
+ }
+ }
+ }
+ }
+ &.legend-right, &.legend-left {
+ gap: 24px;
+ .tb-bar-chart-legend {
+ flex-direction: column-reverse;
+ justify-content: flex-end;
+ align-items: stretch;
+ .tb-bar-chart-legend-item {
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ }
+ }
+ }
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
new file mode 100644
index 0000000000..62c7209dbd
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
@@ -0,0 +1,540 @@
+///
+/// Copyright © 2016-2023 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import {
+ AfterViewInit,
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ Input,
+ OnDestroy,
+ OnInit,
+ Renderer2,
+ TemplateRef,
+ ViewChild,
+ ViewEncapsulation
+} from '@angular/core';
+import { WidgetContext } from '@home/models/widget-component.models';
+import {
+ backgroundStyle,
+ ComponentStyle,
+ DateFormatProcessor,
+ overlayStyle,
+ textStyle
+} from '@shared/models/widget-settings.models';
+import { ResizeObserver } from '@juggle/resize-observer';
+import * as echarts from 'echarts/core';
+import { Axis } from 'echarts';
+import { formatValue } from '@core/utils';
+import { GridComponent, GridComponentOption, TooltipComponent, TooltipComponentOption } from 'echarts/components';
+import { LabelLayout } from 'echarts/features';
+import { BarChart, BarSeriesOption} from 'echarts/charts';
+import { CanvasRenderer } from 'echarts/renderers';
+import { DataKey, DataSet } from '@shared/models/widget.models';
+import { Observable } from 'rxjs';
+import { ImagePipe } from '@shared/pipe/image.pipe';
+import { DomSanitizer } from '@angular/platform-browser';
+import {
+ barChartWithLabelsDefaultSettings,
+ BarChartWithLabelsWidgetSettings
+} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models';
+import { LabelLayoutOptionCallback } from 'echarts/types/dist/shared';
+import BarSeriesModel from 'echarts/types/src/chart/bar/BarSeries';
+
+const axisGetBandWidth = Axis.prototype.getBandWidth;
+
+Axis.prototype.getBandWidth = function(){
+ const series: any[] = this.model?.parentModel?.getSeries();
+ if (this.scale.type === 'time' && series?.length && series[0].type === 'series.bar') {
+ const barWidth = (series[0] as BarSeriesModel).getData().getLayout('size');
+ return barWidth * (series.length + 1);
+ } else {
+ return axisGetBandWidth.call(this);
+ }
+};
+
+echarts.use([
+ TooltipComponent,
+ GridComponent,
+ BarChart,
+ LabelLayout,
+ CanvasRenderer
+]);
+
+type EChartsOption = echarts.ComposeOption<
+ | TooltipComponentOption
+ | GridComponentOption
+ | BarSeriesOption
+>;
+
+type ECharts = echarts.ECharts;
+
+type NamedDataSet = {name: string; value: [number, any]}[];
+
+type BarLabelOption = NonNullable;
+
+interface BarChartDataItem {
+ id: string;
+ dataKey: DataKey;
+ data: NamedDataSet;
+ enabled: boolean;
+}
+
+interface BarChartLegendItem {
+ id: string;
+ color: string;
+ label: string;
+ enabled: boolean;
+}
+
+const toNamedData = (data: DataSet): NamedDataSet => {
+ if (!data?.length) {
+ return [];
+ } else {
+ return data.map(d => ({
+ name: d[0] + '',
+ value: d
+ }));
+ }
+};
+
+@Component({
+ selector: 'tb-bar-chart-with-labels-widget',
+ templateUrl: './bar-chart-with-labels-widget.component.html',
+ styleUrls: ['./bar-chart-with-labels-widget.component.scss'],
+ encapsulation: ViewEncapsulation.None
+})
+export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, AfterViewInit {
+
+ @ViewChild('chartShape', {static: false})
+ chartShape: ElementRef;
+
+ settings: BarChartWithLabelsWidgetSettings;
+
+ @Input()
+ ctx: WidgetContext;
+
+ @Input()
+ widgetTitlePanel: TemplateRef;
+
+ showLegend: boolean;
+ legendClass: string;
+
+ backgroundStyle$: Observable;
+ overlayStyle: ComponentStyle = {};
+
+ legendItems: BarChartLegendItem[];
+ legendLabelStyle: ComponentStyle;
+ disabledLegendLabelStyle: ComponentStyle;
+
+ private shapeResize$: ResizeObserver;
+
+ private decimals = 0;
+ private units = '';
+
+ private dataItems: BarChartDataItem[] = [];
+
+ private drawChartPending = false;
+ private barChart: ECharts;
+ private barChartOptions: EChartsOption;
+
+ private tooltipDateFormat: DateFormatProcessor;
+
+ private barLabelOptions: BarLabelOption;
+ private barLabelLayoutCallback: LabelLayoutOptionCallback;
+
+ constructor(private imagePipe: ImagePipe,
+ private sanitizer: DomSanitizer,
+ private renderer: Renderer2,
+ private cd: ChangeDetectorRef) {
+ }
+
+ ngOnInit(): void {
+ this.ctx.$scope.barChartWidget = this;
+ this.settings = {...barChartWithLabelsDefaultSettings, ...this.ctx.settings};
+
+ this.decimals = this.ctx.decimals;
+ this.units = this.ctx.units;
+
+ this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
+ this.overlayStyle = overlayStyle(this.settings.background.overlay);
+
+ this.showLegend = this.settings.showLegend;
+
+ if (this.showLegend) {
+ this.legendItems = [];
+ this.legendClass = `legend-${this.settings.legendPosition}`;
+ this.legendLabelStyle = textStyle(this.settings.legendLabelFont);
+ this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont);
+ this.legendLabelStyle.color = this.settings.legendLabelColor;
+ }
+ let counter = 0;
+ if (this.ctx.datasources.length) {
+ for (const datasource of this.ctx.datasources) {
+ const dataKeys = datasource.dataKeys;
+ for (const dataKey of dataKeys) {
+ const id = counter++;
+ const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === dataKey) : null;
+ const namedData = datasourceData?.data ? toNamedData(datasourceData.data) : [];
+ this.dataItems.push({
+ id: id+'',
+ dataKey,
+ data: namedData,
+ enabled: true
+ });
+ if (this.showLegend) {
+ this.legendItems.push(
+ {
+ id: id+'',
+ label: dataKey.label,
+ color: dataKey.color,
+ enabled: true
+ }
+ );
+ }
+ }
+ }
+ }
+
+ if (this.settings.showTooltip && this.settings.tooltipShowDate) {
+ this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat);
+ }
+
+ const barValueStyle: ComponentStyle = textStyle(this.settings.barValueFont);
+ delete barValueStyle.lineHeight;
+ barValueStyle.fontSize = this.settings.barValueFont.size;
+ barValueStyle.color = this.settings.barValueColor;
+
+ const barLabelStyle: ComponentStyle = textStyle(this.settings.barLabelFont);
+ delete barLabelStyle.lineHeight;
+ barLabelStyle.fontSize = this.settings.barLabelFont.size;
+ barLabelStyle.color = this.settings.barLabelColor;
+
+ this.barLabelOptions = {
+ show: this.settings.showBarLabel || this.settings.showBarValue,
+ position: 'insideBottom',
+ distance: 15,
+ align: 'left',
+ verticalAlign: 'middle',
+ rotate: 90,
+ formatter: (params) => {
+ const parts: string[] = [];
+ if (this.settings.showBarValue) {
+ const value = formatValue(params.value[1], this.decimals, '', false);
+ parts.push(`{value|${value}}`);
+ }
+ if (this.settings.showBarLabel) {
+ parts.push(`{label|${params.seriesName}}`);
+ }
+ return parts.join(' ');
+ },
+ rich: {
+ value: barValueStyle,
+ label: barLabelStyle
+ }
+ };
+
+ this.barLabelLayoutCallback = (params) => {
+ if (params.rect.width - params.labelRect.width < 2) {
+ return {
+ y: '-100%'
+ };
+ } else {
+ return {
+ hideOverlap: true
+ };
+ }
+ };
+ }
+
+ ngAfterViewInit() {
+ if (this.drawChartPending) {
+ this.drawChart();
+ }
+ }
+
+ ngOnDestroy() {
+ if (this.shapeResize$) {
+ this.shapeResize$.disconnect();
+ }
+ if (this.barChart) {
+ this.barChart.dispose();
+ }
+ }
+
+ public onInit() {
+ const borderRadius = this.ctx.$widgetElement.css('borderRadius');
+ this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
+ if (this.chartShape) {
+ this.drawChart();
+ } else {
+ this.drawChartPending = true;
+ }
+ this.cd.detectChanges();
+ }
+
+ public onDataUpdated() {
+ let minTime = this.ctx.defaultSubscription.timeWindow.minTime;
+ for (const item of this.dataItems) {
+ const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === item.dataKey) : null;
+ const namedData = datasourceData?.data ? toNamedData(datasourceData.data) : [];
+ item.data = namedData;
+ if (datasourceData.data.length) {
+ minTime = Math.min(datasourceData.data[0][0], minTime);
+ }
+ }
+ if (this.barChart) {
+ (this.barChartOptions.xAxis as any).min = minTime;
+ (this.barChartOptions.xAxis as any).max = this.ctx.defaultSubscription.timeWindow.maxTime;
+ this.barChartOptions.series = this.updateSeries();
+ this.barChart.setOption(this.barChartOptions);
+ }
+ }
+
+ private updateSeries(): Array {
+ const series: Array = [];
+ for (const item of this.dataItems) {
+ if (item.enabled) {
+ const seriesOption: BarSeriesOption = {
+ type: 'bar',
+ barGap: 0,
+ barMaxWidth: 40,
+ id: item.id,
+ name: item.dataKey.label,
+ label: this.barLabelOptions,
+ color: item.dataKey.color,
+ data: item.data,
+ emphasis: {
+ focus: 'series'
+ },
+ labelLayout: this.barLabelLayoutCallback
+ };
+ series.push(seriesOption);
+ }
+ }
+ return series;
+ }
+
+ public onLegendItemEnter(item: BarChartLegendItem) {
+ this.barChart.dispatchAction({
+ type: 'highlight',
+ seriesId: item.id
+ });
+ }
+
+ public onLegendItemLeave(item: BarChartLegendItem) {
+ this.barChart.dispatchAction({
+ type: 'downplay',
+ seriesId: item.id
+ });
+ }
+
+ public toggleLegendItem(item: BarChartLegendItem) {
+ const enable = !item.enabled;
+ const dataItem = this.dataItems.find(d => d.id === item.id);
+ if (dataItem) {
+ dataItem.enabled = enable;
+ this.barChartOptions.series = this.updateSeries();
+ this.barChart.setOption(this.barChartOptions, true);
+ item.enabled = enable;
+ if (enable) {
+ this.barChart.dispatchAction({
+ type: 'highlight',
+ seriesId: item.id
+ });
+ } else {
+ for (const otherItem of this.legendItems.filter(i => i.id !== item.id)) {
+ this.barChart.dispatchAction({
+ type: 'highlight',
+ seriesId: otherItem.id
+ });
+ }
+ }
+ }
+ }
+
+ private drawChart() {
+ this.barChart = echarts.init(this.chartShape.nativeElement, null, {
+ renderer: 'canvas',
+ });
+ this.barChartOptions = {
+ tooltip: {
+ trigger: 'none'
+ },
+ grid: {
+ containLabel: true,
+ top: '30',
+ left: 0,
+ right: 0,
+ bottom: 0
+ },
+ xAxis: {
+ type: 'time',
+ axisTick: {
+ show: false
+ },
+ axisLabel: {
+ hideOverlap: true,
+ fontSize: 10
+ },
+ axisLine: {
+ onZero: false
+ },
+ min: this.ctx.defaultSubscription.timeWindow.minTime,
+ max: this.ctx.defaultSubscription.timeWindow.maxTime
+ },
+ yAxis: {
+ type: 'value',
+ axisLabel: {
+ formatter: value => formatValue(value, this.decimals, this.units, false)
+ }
+ }
+ };
+
+ this.barChartOptions.series = this.updateSeries();
+
+ if (this.settings.showTooltip) {
+ this.barChartOptions.tooltip = {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ },
+ formatter: (params: any[]) => {
+ if (!params.length || !params[0]) {
+ return null;
+ }
+ const tooltipElement: HTMLElement = this.renderer.createElement('div');
+ this.renderer.setStyle(tooltipElement, 'display', 'flex');
+ this.renderer.setStyle(tooltipElement, 'flex-direction', 'column');
+ this.renderer.setStyle(tooltipElement, 'align-items', 'flex-start');
+ this.renderer.setStyle(tooltipElement, 'gap', '4px');
+ if (this.settings.tooltipShowDate) {
+ const dateElement: HTMLElement = this.renderer.createElement('div');
+ const ts = params[0].value[0];
+ this.tooltipDateFormat.update(ts);
+ this.renderer.appendChild(dateElement, this.renderer.createText(this.tooltipDateFormat.formatted));
+ this.renderer.setStyle(dateElement, 'font-family', this.settings.tooltipDateFont.family);
+ this.renderer.setStyle(dateElement, 'font-size', this.settings.tooltipDateFont.size + this.settings.tooltipDateFont.sizeUnit);
+ this.renderer.setStyle(dateElement, 'font-style', this.settings.tooltipDateFont.style);
+ this.renderer.setStyle(dateElement, 'font-weight', this.settings.tooltipDateFont.weight);
+ this.renderer.setStyle(dateElement, 'line-height', this.settings.tooltipDateFont.lineHeight);
+ this.renderer.setStyle(dateElement, 'color', this.settings.tooltipDateColor);
+ this.renderer.appendChild(tooltipElement, dateElement);
+ }
+ let seriesParams = null;
+ const focusedSeriesIndex = this.focusedSeriesIndex();
+ if (focusedSeriesIndex > -1) {
+ seriesParams = params.find(param => param.seriesIndex === focusedSeriesIndex);
+ }
+ if (seriesParams) {
+ this.renderer.appendChild(tooltipElement, this.constructTooltipSeriesElement(seriesParams));
+ } else {
+ for (seriesParams of params) {
+ this.renderer.appendChild(tooltipElement, this.constructTooltipSeriesElement(seriesParams));
+ }
+ }
+ return tooltipElement;
+ },
+ padding: [8, 12],
+ backgroundColor: this.settings.tooltipBackgroundColor,
+ extraCssText: `line-height: 1; backdrop-filter: blur(${this.settings.tooltipBackgroundBlur}px);`
+ };
+ }
+
+ this.barChart.setOption(this.barChartOptions);
+
+ this.shapeResize$ = new ResizeObserver(() => {
+ this.onResize();
+ });
+ this.shapeResize$.observe(this.chartShape.nativeElement);
+ this.onResize();
+ }
+
+ private constructTooltipSeriesElement(seriesParams): HTMLElement {
+ const labelValueElement: HTMLElement = this.renderer.createElement('div');
+ this.renderer.setStyle(labelValueElement, 'display', 'flex');
+ this.renderer.setStyle(labelValueElement, 'flex-direction', 'row');
+ this.renderer.setStyle(labelValueElement, 'align-items', 'center');
+ this.renderer.setStyle(labelValueElement, 'align-self', 'stretch');
+ this.renderer.setStyle(labelValueElement, 'gap', '12px');
+ const labelElement: HTMLElement = this.renderer.createElement('div');
+ this.renderer.setStyle(labelElement, 'display', 'flex');
+ this.renderer.setStyle(labelElement, 'align-items', 'center');
+ this.renderer.setStyle(labelElement, 'gap', '8px');
+ this.renderer.appendChild(labelValueElement, labelElement);
+ const circleElement: HTMLElement = this.renderer.createElement('div');
+ this.renderer.setStyle(circleElement, 'width', '8px');
+ this.renderer.setStyle(circleElement, 'height', '8px');
+ this.renderer.setStyle(circleElement, 'border-radius', '50%');
+ this.renderer.setStyle(circleElement, 'background', seriesParams.color);
+ this.renderer.appendChild(labelElement, circleElement);
+ const labelTextElement: HTMLElement = this.renderer.createElement('div');
+ this.renderer.appendChild(labelTextElement, this.renderer.createText(seriesParams.seriesName));
+ this.renderer.setStyle(labelTextElement, 'font-family', 'Roboto');
+ this.renderer.setStyle(labelTextElement, 'font-size', '12px');
+ this.renderer.setStyle(labelTextElement, 'font-style', 'normal');
+ this.renderer.setStyle(labelTextElement, 'font-weight', '400');
+ this.renderer.setStyle(labelTextElement, 'line-height', '16px');
+ this.renderer.setStyle(labelTextElement, 'letter-spacing', '0.4px');
+ this.renderer.setStyle(labelTextElement, 'color', 'rgba(0, 0, 0, 0.76)');
+ this.renderer.appendChild(labelElement, labelTextElement);
+ const valueElement: HTMLElement = this.renderer.createElement('div');
+ const value = formatValue(seriesParams.value[1], this.decimals, this.units, false);
+ this.renderer.appendChild(valueElement, this.renderer.createText(value));
+ this.renderer.setStyle(valueElement, 'flex', '1');
+ this.renderer.setStyle(valueElement, 'text-align', 'end');
+ this.renderer.setStyle(valueElement, 'font-family', this.settings.tooltipValueFont.family);
+ this.renderer.setStyle(valueElement, 'font-size', this.settings.tooltipValueFont.size + this.settings.tooltipValueFont.sizeUnit);
+ this.renderer.setStyle(valueElement, 'font-style', this.settings.tooltipValueFont.style);
+ this.renderer.setStyle(valueElement, 'font-weight', this.settings.tooltipValueFont.weight);
+ this.renderer.setStyle(valueElement, 'line-height', this.settings.tooltipValueFont.lineHeight);
+ this.renderer.setStyle(valueElement, 'color', this.settings.tooltipValueColor);
+ this.renderer.appendChild(labelValueElement, valueElement);
+ return labelValueElement;
+ }
+
+ private focusedSeriesIndex(): number {
+ let index = - 1;
+ // @ts-ignore
+ const views: any[] = this.barChart._chartsViews;
+ if (views) {
+ const hasBlurredView = !!views.find(view => {
+ const graphicEls: any[] = view._data._graphicEls;
+ return !!graphicEls.find(el => el.currentStates.includes('blur'));
+ });
+ if (hasBlurredView) {
+ const focusedView = views.find(view => {
+ const graphicEls: any[] = view._data._graphicEls;
+ return !!graphicEls.find(el => !el.currentStates.includes('blur'));
+ });
+ if (focusedView) {
+ index = focusedView._model.seriesIndex;
+ }
+ }
+ }
+ return index;
+ }
+
+ private onResize() {
+ const width = this.barChart.getWidth();
+ const height = this.barChart.getHeight();
+ const shapeWidth = this.chartShape.nativeElement.offsetWidth;
+ const shapeHeight = this.chartShape.nativeElement.offsetHeight;
+ if (width !== shapeWidth || height !== shapeHeight) {
+ this.barChart.resize();
+ }
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts
new file mode 100644
index 0000000000..b56b2bc435
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts
@@ -0,0 +1,112 @@
+///
+/// Copyright © 2016-2023 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import {
+ BackgroundSettings,
+ BackgroundType, customDateFormat,
+ DateFormatSettings,
+ Font
+} from '@shared/models/widget-settings.models';
+import { LegendPosition } from '@shared/models/widget.models';
+
+export interface BarChartWithLabelsWidgetSettings {
+ showBarLabel: boolean;
+ barLabelFont: Font;
+ barLabelColor: string;
+ showBarValue: boolean;
+ barValueFont: Font;
+ barValueColor: string;
+ showLegend: boolean;
+ legendPosition: LegendPosition;
+ legendLabelFont: Font;
+ legendLabelColor: string;
+ showTooltip: boolean;
+ tooltipValueFont: Font;
+ tooltipValueColor: string;
+ tooltipShowDate: boolean;
+ tooltipDateFormat: DateFormatSettings;
+ tooltipDateFont: Font;
+ tooltipDateColor: string;
+ tooltipBackgroundColor: string;
+ tooltipBackgroundBlur: number;
+ background: BackgroundSettings;
+}
+
+export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings = {
+ showBarLabel: true,
+ barLabelFont: {
+ family: 'Roboto',
+ size: 12,
+ sizeUnit: 'px',
+ style: 'normal',
+ weight: '400',
+ lineHeight: '12px'
+ },
+ barLabelColor: 'rgba(0, 0, 0, 0.54)',
+ showBarValue: true,
+ barValueFont: {
+ family: 'Roboto',
+ size: 12,
+ sizeUnit: 'px',
+ style: 'normal',
+ weight: '700',
+ lineHeight: '12px'
+ },
+ barValueColor: 'rgba(0, 0, 0, 0.76)',
+ showLegend: true,
+ legendPosition: LegendPosition.top,
+ legendLabelFont: {
+ family: 'Roboto',
+ size: 12,
+ sizeUnit: 'px',
+ style: 'normal',
+ weight: '400',
+ lineHeight: '16px'
+ },
+ legendLabelColor: 'rgba(0, 0, 0, 0.76)',
+ showTooltip: true,
+ tooltipValueFont: {
+ family: 'Roboto',
+ size: 12,
+ sizeUnit: 'px',
+ style: 'normal',
+ weight: '500',
+ lineHeight: '16px'
+ },
+ tooltipValueColor: 'rgba(0, 0, 0, 0.76)',
+ tooltipShowDate: true,
+ tooltipDateFormat: customDateFormat('MMMM y'),
+ tooltipDateFont: {
+ family: 'Roboto',
+ size: 11,
+ sizeUnit: 'px',
+ style: 'normal',
+ weight: '400',
+ lineHeight: '16px'
+ },
+ tooltipDateColor: 'rgba(0, 0, 0, 0.76)',
+ tooltipBackgroundColor: 'rgba(255, 255, 255, 0.76)',
+ tooltipBackgroundBlur: 4,
+ background: {
+ type: BackgroundType.color,
+ color: '#fff',
+ overlay: {
+ enabled: false,
+ color: 'rgba(255,255,255,0.72)',
+ blur: 3
+ }
+ }
+};
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
index 6679ed0cca..15568db522 100644
--- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
@@ -67,6 +67,9 @@ import { ProgressBarWidgetComponent } from '@home/components/widget/lib/cards/pr
import { LiquidLevelWidgetComponent } from '@home/components/widget/lib/indicator/liquid-level-widget.component';
import { DoughnutWidgetComponent } from '@home/components/widget/lib/chart/doughnut-widget.component';
import { RangeChartWidgetComponent } from '@home/components/widget/lib/chart/range-chart-widget.component';
+import {
+ BarChartWithLabelsWidgetComponent
+} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.component';
@NgModule({
declarations:
@@ -108,7 +111,8 @@ import { RangeChartWidgetComponent } from '@home/components/widget/lib/chart/ran
ProgressBarWidgetComponent,
LiquidLevelWidgetComponent,
DoughnutWidgetComponent,
- RangeChartWidgetComponent
+ RangeChartWidgetComponent,
+ BarChartWithLabelsWidgetComponent
],
imports: [
CommonModule,
@@ -154,7 +158,8 @@ import { RangeChartWidgetComponent } from '@home/components/widget/lib/chart/ran
ProgressBarWidgetComponent,
LiquidLevelWidgetComponent,
DoughnutWidgetComponent,
- RangeChartWidgetComponent
+ RangeChartWidgetComponent,
+ BarChartWithLabelsWidgetComponent
],
providers: [
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }