diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html index c237c056d9..0c5eb426b3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html @@ -318,8 +318,8 @@ {{ 'tooltip.hide-zero-tooltip-values' | translate }} -
- +
+ {{ 'tooltip.show-stack-total' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts index 4223ce139b..60135a3438 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts @@ -363,10 +363,11 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon this.timeSeriesChartWidgetConfigForm.get('tooltipValueColor').enable(); this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').enable({emitEvent: false}); this.timeSeriesChartWidgetConfigForm.get('tooltipHideZeroValues').enable({emitEvent: false}); - if (stack) + if (stack) { this.timeSeriesChartWidgetConfigForm.get('tooltipStackedShowTotal').enable(); - else + } else { this.timeSeriesChartWidgetConfigForm.get('tooltipStackedShowTotal').disable(); + } this.timeSeriesChartWidgetConfigForm.get('tooltipBackgroundColor').enable(); this.timeSeriesChartWidgetConfigForm.get('tooltipBackgroundBlur').enable(); if (tooltipShowDate) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-tooltip.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-tooltip.models.ts index 77d32149a2..e7f3de8f31 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-tooltip.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-tooltip.models.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { isFunction } from '@core/utils'; +import { isDefined, isFunction, isNotEmptyStr } from '@core/utils'; import { FormattedData } from '@shared/models/widget.models'; import { DateFormatProcessor, DateFormatSettings, Font } from '@shared/models/widget-settings.models'; import { TimeSeriesChartDataItem } from '@home/components/widget/lib/chart/time-series-chart.models'; @@ -22,6 +22,7 @@ import { Renderer2, SecurityContext } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import { CallbackDataParams } from 'echarts/types/dist/shared'; import { Interval } from '@shared/models/time/time.models'; +import { TranslateService } from '@ngx-translate/core'; export type TimeSeriesChartTooltipValueFormatFunction = (value: any, latestData: FormattedData, units?: string, decimals?: number) => string; @@ -89,7 +90,8 @@ export class TimeSeriesChartTooltip { private sanitizer: DomSanitizer, private settings: TimeSeriesChartTooltipWidgetSettings, private tooltipDateFormat: DateFormatProcessor, - private valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction) { + private valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction, + private translate: TranslateService) { } @@ -131,23 +133,65 @@ export class TimeSeriesChartTooltip { if (this.settings.tooltipShowDate) { this.renderer.appendChild(tooltipItemsElement, this.constructTooltipDateElement(items[0].param, interval)); } - let total = 0, isStacked = false; + let total = 0; + let isStacked = false; + const totalUnits = new Set(); + let totalDecimal = 0; for (const item of items) { - if (!this.settings.tooltipHideZeroValues || (item.param.value[1] && item.param.value[1] !== 'false')) { + if (this.shouldShowItem(item)) { this.renderer.appendChild(tooltipItemsElement, this.constructTooltipSeriesElement(item)); - if (item.dataItem?.barRenderContext?.barStackIndex !== undefined && !isNaN(Number(item.param.value[1]))) { + if (item.dataItem?.option?.stack !== undefined && !isNaN(Number(item.param.value[1]))) { isStacked = true; total += Number(item.param.value[1]); + if (isNotEmptyStr(item.dataItem.units)) { + totalUnits.add(item.dataItem.units); + } + if (isDefined(item.dataItem.decimals)) { + totalDecimal = Math.max(item.dataItem.decimals, totalDecimal); + } } } } - if (isStacked && this.settings.tooltipStackedShowTotal) - this.renderer.appendChild(tooltipItemsElement, this.constructTooltipTotalStackedElement(total)); + if (isStacked && this.settings.tooltipStackedShowTotal) { + const unit = totalUnits.size === 1 ? Array.from(totalUnits.values())[0] : ""; + const totalValue = this.valueFormatFunction(total, {} as FormattedData, unit, totalDecimal); + this.renderer.appendChild(tooltipItemsElement, this.constructTooltipTotalStackedElement(totalValue)); + } + } + } + + private shouldShowItem(item: TooltipItem): boolean { + if (!this.settings.tooltipHideZeroValues) return true; + const value = item.param?.value?.[1]; + return value && value !== 'false'; + } + + private createElement(tag = 'div', styles?: Record): HTMLElement { + const node = this.renderer.createElement(tag); + if (styles) { + for (const [k, v] of Object.entries(styles)) { + this.renderer.setStyle(node, k, v); + } + } + return node; + } + + private applyFont(el: HTMLElement, font: {family: string; size: number; sizeUnit: string; style: string; weight: string; lineHeight: string}, color: string, overrides?: Partial) { + this.renderer.setStyle(el, 'font-family', font.family); + this.renderer.setStyle(el, 'font-size', `${font.size}${font.sizeUnit}`); + this.renderer.setStyle(el, 'font-style', font.style); + this.renderer.setStyle(el, 'font-weight', font.weight); + this.renderer.setStyle(el, 'line-height', font.lineHeight); + this.renderer.setStyle(el, 'color', color); + if (overrides) { + for (const [k, v] of Object.entries(overrides)) { + if (v != null) this.renderer.setStyle(el, k, v as string); + } } } private constructTooltipDateElement(param: CallbackDataParams, interval?: Interval): HTMLElement { - const dateElement: HTMLElement = this.renderer.createElement('div'); + const dateElement = this.createElement(); let dateText: string; const startTs = param.value[2]; const endTs = param.value[3]; @@ -164,43 +208,25 @@ export class TimeSeriesChartTooltip { dateText = this.tooltipDateFormat.update(ts, interval); } this.renderer.appendChild(dateElement, this.renderer.createText(dateText)); - 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.applyFont(dateElement, this.settings.tooltipDateFont, this.settings.tooltipDateColor); return dateElement; } private constructTooltipSeriesElement(item: TooltipItem): 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', item.param.color); - this.renderer.appendChild(labelElement, circleElement); - const labelTextElement: HTMLElement = this.renderer.createElement('div'); - this.renderer.setProperty(labelTextElement, 'innerHTML', this.sanitizer.sanitize(SecurityContext.HTML, item.param.seriesName)); - this.renderer.setStyle(labelTextElement, 'font-family', this.settings.tooltipLabelFont.family); - this.renderer.setStyle(labelTextElement, 'font-size', this.settings.tooltipLabelFont.size + this.settings.tooltipLabelFont.sizeUnit); - this.renderer.setStyle(labelTextElement, 'font-style', this.settings.tooltipLabelFont.style); - this.renderer.setStyle(labelTextElement, 'font-weight', this.settings.tooltipLabelFont.weight); - this.renderer.setStyle(labelTextElement, 'line-height', this.settings.tooltipLabelFont.lineHeight); - this.renderer.setStyle(labelTextElement, 'color', this.settings.tooltipLabelColor); - this.renderer.appendChild(labelElement, labelTextElement); - const valueElement: HTMLElement = this.renderer.createElement('div'); + const row = this.createElement('div', {display: 'flex', 'flex-direction': 'row', 'align-items': 'center', 'align-self': 'stretch', gap: '12px'}); + + const label = this.createElement('div', { display: 'flex', 'align-items': 'center', gap: '8px' }); + this.renderer.appendChild(row, label); + + const dot = this.createElement('div', { width: '8px', height: '8px', 'border-radius': '50%', background: item.param.color as string }); + this.renderer.appendChild(label, dot); + + const labelText = this.createElement('div'); + this.renderer.setProperty(labelText, 'innerHTML', this.sanitizer.sanitize(SecurityContext.HTML, item.param.seriesName)); + this.applyFont(labelText, this.settings.tooltipLabelFont, this.settings.tooltipLabelColor); + this.renderer.appendChild(label, labelText); + + const valueElement: HTMLElement = this.createElement('div', { flex: '1', 'text-align': 'end' }); let formatFunction = this.valueFormatFunction; let latestData: FormattedData; let units = ''; @@ -218,51 +244,29 @@ export class TimeSeriesChartTooltip { } const value = formatFunction(item.param.value[1], latestData, units, decimals); this.renderer.setProperty(valueElement, 'innerHTML', this.sanitizer.sanitize(SecurityContext.HTML, 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; + this.applyFont(valueElement, this.settings.tooltipValueFont, this.settings.tooltipValueColor); + this.renderer.appendChild(row, valueElement); + + return row; } - private constructTooltipTotalStackedElement(total: number): 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 labelTextElement: HTMLElement = this.renderer.createElement('div'); - this.renderer.setProperty(labelTextElement, 'innerHTML', this.sanitizer.sanitize(SecurityContext.HTML, 'Total')); - this.renderer.setStyle(labelTextElement, 'font-family', this.settings.tooltipLabelFont.family); - this.renderer.setStyle(labelTextElement, 'font-size', this.settings.tooltipLabelFont.size + this.settings.tooltipLabelFont.sizeUnit); - this.renderer.setStyle(labelTextElement, 'font-style', this.settings.tooltipLabelFont.style); - this.renderer.setStyle(labelTextElement, 'font-weight', 'bold'); - this.renderer.setStyle(labelTextElement, 'line-height', this.settings.tooltipLabelFont.lineHeight); - this.renderer.setStyle(labelTextElement, 'color', this.settings.tooltipLabelColor); - this.renderer.appendChild(labelElement, labelTextElement); - const valueElement: HTMLElement = this.renderer.createElement('div'); - this.renderer.setProperty(valueElement, 'innerHTML', this.sanitizer.sanitize(SecurityContext.HTML, total.toString())); - 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', 'bold'); - 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 constructTooltipTotalStackedElement(total: string): HTMLElement { + const row = this.createElement('div', {display: 'flex', 'flex-direction': 'row', 'align-items': 'center', 'align-self': 'stretch', gap: '12px'}); + + const label = this.createElement('div', { display: 'flex', 'align-items': 'center', gap: '8px' }); + this.renderer.appendChild(row, label); + + const labelText = this.createElement('div'); + this.renderer.setProperty(labelText, 'innerHTML', this.sanitizer.sanitize(SecurityContext.HTML, this.translate.instant('legend.Total'))); + this.applyFont(labelText, this.settings.tooltipLabelFont, this.settings.tooltipLabelColor, { fontWeight: 'bold' }); + this.renderer.appendChild(label, labelText); + + const valueEl = this.createElement('div', { flex: '1', 'text-align': 'end' }); + this.renderer.setProperty(valueEl, 'innerHTML', this.sanitizer.sanitize(SecurityContext.HTML, total)); + this.applyFont(valueEl, this.settings.tooltipValueFont, this.settings.tooltipValueColor, { fontWeight: 'bold' }); + this.renderer.appendChild(row, valueEl); + + return row; } private static mapTooltipParams(params: CallbackDataParams[] | CallbackDataParams, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index d18f985c1d..089c655224 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -143,7 +143,7 @@ export interface TimeSeriesChartDataItem { xAxisIndex: number; yAxisId: TimeSeriesChartYAxisId; yAxisIndex: number; - option?: LineSeriesOption | CustomSeriesOption; + option?: LineSeriesOption; barRenderContext?: BarRenderContext; unitConvertor?: TbUnitConverter; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 7ac6e3ec85..d447b51b37 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -212,7 +212,8 @@ export class TbTimeSeriesChart { this.ctx.sanitizer, this.settings, this.tooltipDateFormat, - tooltipValueFormatFunction + tooltipValueFormatFunction, + this.ctx.translate ); this.onResize(); if (this.autoResize) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html index 9914a6124d..9a80362c8d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html @@ -242,8 +242,8 @@ {{ 'tooltip.hide-zero-tooltip-values' | translate }}
-
- +
+ {{ 'tooltip.show-stack-total' | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts index a753b12acd..0c2f00d09b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts @@ -227,10 +227,11 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon this.timeSeriesChartWidgetSettingsForm.get('tooltipValueFormatter').enable(); this.timeSeriesChartWidgetSettingsForm.get('tooltipShowDate').enable({emitEvent: false}); this.timeSeriesChartWidgetSettingsForm.get('tooltipHideZeroValues').enable(); - if (stack) + if (stack) { this.timeSeriesChartWidgetSettingsForm.get('tooltipStackedShowTotal').enable(); - else + } else { this.timeSeriesChartWidgetSettingsForm.get('tooltipStackedShowTotal').disable(); + } this.timeSeriesChartWidgetSettingsForm.get('tooltipBackgroundColor').enable(); this.timeSeriesChartWidgetSettingsForm.get('tooltipBackgroundBlur').enable(); if (tooltipShowDate) {