|
|
@ -14,7 +14,7 @@ |
|
|
/// limitations under the License.
|
|
|
/// limitations under the License.
|
|
|
///
|
|
|
///
|
|
|
|
|
|
|
|
|
import { isFunction } from '@core/utils'; |
|
|
import { isDefined, isFunction, isNotEmptyStr } from '@core/utils'; |
|
|
import { FormattedData } from '@shared/models/widget.models'; |
|
|
import { FormattedData } from '@shared/models/widget.models'; |
|
|
import { DateFormatProcessor, DateFormatSettings, Font } from '@shared/models/widget-settings.models'; |
|
|
import { DateFormatProcessor, DateFormatSettings, Font } from '@shared/models/widget-settings.models'; |
|
|
import { TimeSeriesChartDataItem } from '@home/components/widget/lib/chart/time-series-chart.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 { DomSanitizer } from '@angular/platform-browser'; |
|
|
import { CallbackDataParams } from 'echarts/types/dist/shared'; |
|
|
import { CallbackDataParams } from 'echarts/types/dist/shared'; |
|
|
import { Interval } from '@shared/models/time/time.models'; |
|
|
import { Interval } from '@shared/models/time/time.models'; |
|
|
|
|
|
import { TranslateService } from '@ngx-translate/core'; |
|
|
|
|
|
|
|
|
export type TimeSeriesChartTooltipValueFormatFunction = |
|
|
export type TimeSeriesChartTooltipValueFormatFunction = |
|
|
(value: any, latestData: FormattedData, units?: string, decimals?: number) => string; |
|
|
(value: any, latestData: FormattedData, units?: string, decimals?: number) => string; |
|
|
@ -43,6 +44,7 @@ export interface TimeSeriesChartTooltipWidgetSettings { |
|
|
tooltipDateColor: string; |
|
|
tooltipDateColor: string; |
|
|
tooltipBackgroundColor: string; |
|
|
tooltipBackgroundColor: string; |
|
|
tooltipBackgroundBlur: number; |
|
|
tooltipBackgroundBlur: number; |
|
|
|
|
|
tooltipStackedShowTotal?: boolean |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export enum TimeSeriesChartTooltipTrigger { |
|
|
export enum TimeSeriesChartTooltipTrigger { |
|
|
@ -88,7 +90,8 @@ export class TimeSeriesChartTooltip { |
|
|
private sanitizer: DomSanitizer, |
|
|
private sanitizer: DomSanitizer, |
|
|
private settings: TimeSeriesChartTooltipWidgetSettings, |
|
|
private settings: TimeSeriesChartTooltipWidgetSettings, |
|
|
private tooltipDateFormat: DateFormatProcessor, |
|
|
private tooltipDateFormat: DateFormatProcessor, |
|
|
private valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction) { |
|
|
private valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction, |
|
|
|
|
|
private translate: TranslateService) { |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -130,16 +133,65 @@ export class TimeSeriesChartTooltip { |
|
|
if (this.settings.tooltipShowDate) { |
|
|
if (this.settings.tooltipShowDate) { |
|
|
this.renderer.appendChild(tooltipItemsElement, this.constructTooltipDateElement(items[0].param, interval)); |
|
|
this.renderer.appendChild(tooltipItemsElement, this.constructTooltipDateElement(items[0].param, interval)); |
|
|
} |
|
|
} |
|
|
|
|
|
let total = 0; |
|
|
|
|
|
let isStacked = false; |
|
|
|
|
|
const totalUnits = new Set<string>(); |
|
|
|
|
|
let totalDecimal = 0; |
|
|
for (const item of items) { |
|
|
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)); |
|
|
this.renderer.appendChild(tooltipItemsElement, this.constructTooltipSeriesElement(item)); |
|
|
|
|
|
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) { |
|
|
|
|
|
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<string, string>): 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<CSSStyleDeclaration>) { |
|
|
|
|
|
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 { |
|
|
private constructTooltipDateElement(param: CallbackDataParams, interval?: Interval): HTMLElement { |
|
|
const dateElement: HTMLElement = this.renderer.createElement('div'); |
|
|
const dateElement = this.createElement(); |
|
|
let dateText: string; |
|
|
let dateText: string; |
|
|
const startTs = param.value[2]; |
|
|
const startTs = param.value[2]; |
|
|
const endTs = param.value[3]; |
|
|
const endTs = param.value[3]; |
|
|
@ -156,43 +208,25 @@ export class TimeSeriesChartTooltip { |
|
|
dateText = this.tooltipDateFormat.update(ts, interval); |
|
|
dateText = this.tooltipDateFormat.update(ts, interval); |
|
|
} |
|
|
} |
|
|
this.renderer.appendChild(dateElement, this.renderer.createText(dateText)); |
|
|
this.renderer.appendChild(dateElement, this.renderer.createText(dateText)); |
|
|
this.renderer.setStyle(dateElement, 'font-family', this.settings.tooltipDateFont.family); |
|
|
this.applyFont(dateElement, this.settings.tooltipDateFont, this.settings.tooltipDateColor); |
|
|
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); |
|
|
|
|
|
return dateElement; |
|
|
return dateElement; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private constructTooltipSeriesElement(item: TooltipItem): HTMLElement { |
|
|
private constructTooltipSeriesElement(item: TooltipItem): HTMLElement { |
|
|
const labelValueElement: HTMLElement = this.renderer.createElement('div'); |
|
|
const row = this.createElement('div', {display: 'flex', 'flex-direction': 'row', 'align-items': 'center', 'align-self': 'stretch', gap: '12px'}); |
|
|
this.renderer.setStyle(labelValueElement, 'display', 'flex'); |
|
|
|
|
|
this.renderer.setStyle(labelValueElement, 'flex-direction', 'row'); |
|
|
const label = this.createElement('div', { display: 'flex', 'align-items': 'center', gap: '8px' }); |
|
|
this.renderer.setStyle(labelValueElement, 'align-items', 'center'); |
|
|
this.renderer.appendChild(row, label); |
|
|
this.renderer.setStyle(labelValueElement, 'align-self', 'stretch'); |
|
|
|
|
|
this.renderer.setStyle(labelValueElement, 'gap', '12px'); |
|
|
const dot = this.createElement('div', { width: '8px', height: '8px', 'border-radius': '50%', background: item.param.color as string }); |
|
|
const labelElement: HTMLElement = this.renderer.createElement('div'); |
|
|
this.renderer.appendChild(label, dot); |
|
|
this.renderer.setStyle(labelElement, 'display', 'flex'); |
|
|
|
|
|
this.renderer.setStyle(labelElement, 'align-items', 'center'); |
|
|
const labelText = this.createElement('div'); |
|
|
this.renderer.setStyle(labelElement, 'gap', '8px'); |
|
|
this.renderer.setProperty(labelText, 'innerHTML', this.sanitizer.sanitize(SecurityContext.HTML, item.param.seriesName)); |
|
|
this.renderer.appendChild(labelValueElement, labelElement); |
|
|
this.applyFont(labelText, this.settings.tooltipLabelFont, this.settings.tooltipLabelColor); |
|
|
const circleElement: HTMLElement = this.renderer.createElement('div'); |
|
|
this.renderer.appendChild(label, labelText); |
|
|
this.renderer.setStyle(circleElement, 'width', '8px'); |
|
|
|
|
|
this.renderer.setStyle(circleElement, 'height', '8px'); |
|
|
const valueElement: HTMLElement = this.createElement('div', { flex: '1', 'text-align': 'end' }); |
|
|
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'); |
|
|
|
|
|
let formatFunction = this.valueFormatFunction; |
|
|
let formatFunction = this.valueFormatFunction; |
|
|
let latestData: FormattedData; |
|
|
let latestData: FormattedData; |
|
|
let units = ''; |
|
|
let units = ''; |
|
|
@ -210,19 +244,32 @@ export class TimeSeriesChartTooltip { |
|
|
} |
|
|
} |
|
|
const value = formatFunction(item.param.value[1], latestData, units, decimals); |
|
|
const value = formatFunction(item.param.value[1], latestData, units, decimals); |
|
|
this.renderer.setProperty(valueElement, 'innerHTML', this.sanitizer.sanitize(SecurityContext.HTML, value)); |
|
|
this.renderer.setProperty(valueElement, 'innerHTML', this.sanitizer.sanitize(SecurityContext.HTML, value)); |
|
|
this.renderer.setStyle(valueElement, 'flex', '1'); |
|
|
this.applyFont(valueElement, this.settings.tooltipValueFont, this.settings.tooltipValueColor); |
|
|
this.renderer.setStyle(valueElement, 'text-align', 'end'); |
|
|
this.renderer.appendChild(row, valueElement); |
|
|
this.renderer.setStyle(valueElement, 'font-family', this.settings.tooltipValueFont.family); |
|
|
|
|
|
this.renderer.setStyle(valueElement, 'font-size', this.settings.tooltipValueFont.size + this.settings.tooltipValueFont.sizeUnit); |
|
|
return row; |
|
|
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); |
|
|
private constructTooltipTotalStackedElement(total: string): HTMLElement { |
|
|
this.renderer.setStyle(valueElement, 'color', this.settings.tooltipValueColor); |
|
|
const row = this.createElement('div', {display: 'flex', 'flex-direction': 'row', 'align-items': 'center', 'align-self': 'stretch', gap: '12px'}); |
|
|
this.renderer.appendChild(labelValueElement, valueElement); |
|
|
|
|
|
return labelValueElement; |
|
|
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, |
|
|
private static mapTooltipParams(params: CallbackDataParams[] | CallbackDataParams, |
|
|
series?: TimeSeriesChartDataItem[], |
|
|
series?: TimeSeriesChartDataItem[], |
|
|
focusedSeriesIndex?: number): TooltipParams { |
|
|
focusedSeriesIndex?: number): TooltipParams { |
|
|
const result: TooltipParams = { |
|
|
const result: TooltipParams = { |
|
|
|