Browse Source

Merge pull request #11055 from vvlladd28/improvement/tooltip-chart/unicode

Added support unicode symbols in chart legend/tooltip
pull/11176/head
Igor Kulikov 2 years ago
committed by GitHub
parent
commit
0ff1151623
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts
  2. 4
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts
  3. 4
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts
  4. 6
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-state.models.ts
  5. 263
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-tooltip.models.ts
  6. 26
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html
  7. 252
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  8. 40
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  9. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts

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

@ -46,12 +46,12 @@ import {
TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import {
TimeSeriesChartTooltipTrigger,
TimeSeriesChartKeySettings,
TimeSeriesChartType,
TimeSeriesChartYAxes,
TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { TimeSeriesChartTooltipTrigger } from '@home/components/widget/lib/chart/time-series-chart-tooltip.models';
@Component({
selector: 'tb-time-series-chart-basic-config',

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

@ -35,7 +35,6 @@ import {
TimeSeriesChartSeriesType,
TimeSeriesChartSettings,
TimeSeriesChartThreshold,
TimeSeriesChartTooltipWidgetSettings,
TimeSeriesChartXAxisSettings,
TimeSeriesChartYAxisSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
@ -48,6 +47,9 @@ import {
ChartFillSettings,
ChartFillType
} from '@home/components/widget/lib/chart/chart.models';
import {
TimeSeriesChartTooltipWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-tooltip.models';
export interface BarChartWithLabelsWidgetSettings extends TimeSeriesChartTooltipWidgetSettings {
dataZoom: boolean;

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

@ -38,7 +38,6 @@ import {
TimeSeriesChartSettings,
TimeSeriesChartThreshold,
timeSeriesChartThresholdDefaultSettings,
TimeSeriesChartTooltipWidgetSettings,
TimeSeriesChartVisualMapPiece,
TimeSeriesChartXAxisSettings,
TimeSeriesChartYAxisSettings
@ -54,6 +53,9 @@ import {
ChartLineType,
ChartShape
} from '@home/components/widget/lib/chart/chart.models';
import {
TimeSeriesChartTooltipWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-tooltip.models';
export interface RangeItem {
index: number;

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

@ -18,13 +18,15 @@ import {
TimeSeriesChartStateSettings,
TimeSeriesChartStateSourceType,
TimeSeriesChartTicksFormatter,
TimeSeriesChartTicksGenerator,
TimeSeriesChartTooltipValueFormatFunction
TimeSeriesChartTicksGenerator
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { UtilsService } from '@core/services/utils.service';
import { FormattedData } from '@shared/models/widget.models';
import { formatValue, isDefinedAndNotNull, isNumber, isNumeric } from '@core/utils';
import { LabelFormatterCallback } from 'echarts';
import {
TimeSeriesChartTooltipValueFormatFunction
} from '@home/components/widget/lib/chart/time-series-chart-tooltip.models';
export class TimeSeriesChartStateValueConverter {

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

@ -0,0 +1,263 @@
///
/// 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 { isFunction } 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';
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';
export type TimeSeriesChartTooltipValueFormatFunction =
(value: any, latestData: FormattedData, units?: string, decimals?: number) => string;
export interface TimeSeriesChartTooltipWidgetSettings {
showTooltip: boolean;
tooltipTrigger?: TimeSeriesChartTooltipTrigger;
tooltipShowFocusedSeries?: boolean;
tooltipLabelFont: Font;
tooltipLabelColor: string;
tooltipValueFont: Font;
tooltipValueColor: string;
tooltipValueFormatter?: string | TimeSeriesChartTooltipValueFormatFunction;
tooltipShowDate: boolean;
tooltipDateInterval?: boolean;
tooltipDateFormat: DateFormatSettings;
tooltipDateFont: Font;
tooltipDateColor: string;
tooltipBackgroundColor: string;
tooltipBackgroundBlur: number;
}
export enum TimeSeriesChartTooltipTrigger {
point = 'point',
axis = 'axis'
}
export const tooltipTriggerTranslationMap = new Map<TimeSeriesChartTooltipTrigger, string>(
[
[TimeSeriesChartTooltipTrigger.point, 'tooltip.trigger-point'],
[TimeSeriesChartTooltipTrigger.axis, 'tooltip.trigger-axis']
]
);
interface TooltipItem {
param: CallbackDataParams;
dataItem: TimeSeriesChartDataItem;
}
interface TooltipParams {
items: TooltipItem[];
comparisonItems: TooltipItem[];
}
export const createTooltipValueFormatFunction =
(tooltipValueFormatter: string | TimeSeriesChartTooltipValueFormatFunction): TimeSeriesChartTooltipValueFormatFunction => {
let tooltipValueFormatFunction: TimeSeriesChartTooltipValueFormatFunction;
if (isFunction(tooltipValueFormatter)) {
tooltipValueFormatFunction = tooltipValueFormatter as TimeSeriesChartTooltipValueFormatFunction;
} else if (typeof tooltipValueFormatter === 'string' && tooltipValueFormatter.length) {
try {
tooltipValueFormatFunction =
new Function('value', 'latestData', tooltipValueFormatter) as TimeSeriesChartTooltipValueFormatFunction;
} catch (e) {
}
}
return tooltipValueFormatFunction;
};
export class TimeSeriesChartTooltip {
constructor(private renderer: Renderer2,
private sanitizer: DomSanitizer,
private settings: TimeSeriesChartTooltipWidgetSettings,
private tooltipDateFormat: DateFormatProcessor,
private valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction) {
}
formatted(params: CallbackDataParams[] | CallbackDataParams, focusedSeriesIndex: number,
series?: TimeSeriesChartDataItem[], interval?: Interval): HTMLElement {
if (!this.settings.showTooltip) {
return undefined;
}
const tooltipParams = TimeSeriesChartTooltip.mapTooltipParams(params, series, focusedSeriesIndex);
if (!tooltipParams.items.length && !tooltipParams.comparisonItems.length) {
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', '16px');
this.buildItemsTooltip(tooltipElement, tooltipParams.items, interval);
this.buildItemsTooltip(tooltipElement, tooltipParams.comparisonItems, interval);
return tooltipElement;
}
private buildItemsTooltip(tooltipElement: HTMLElement,
items: TooltipItem[], interval?: Interval) {
if (items.length) {
const tooltipItemsElement: HTMLElement = this.renderer.createElement('div');
this.renderer.setStyle(tooltipItemsElement, 'display', 'flex');
this.renderer.setStyle(tooltipItemsElement, 'flex-direction', 'column');
this.renderer.setStyle(tooltipItemsElement, 'align-items', 'flex-start');
this.renderer.setStyle(tooltipItemsElement, 'gap', '4px');
this.renderer.appendChild(tooltipElement, tooltipItemsElement);
if (this.settings.tooltipShowDate) {
this.renderer.appendChild(tooltipItemsElement, this.constructTooltipDateElement(items[0].param, interval));
}
for (const item of items) {
this.renderer.appendChild(tooltipItemsElement, this.constructTooltipSeriesElement(item));
}
}
}
private constructTooltipDateElement(param: CallbackDataParams, interval?: Interval): HTMLElement {
const dateElement: HTMLElement = this.renderer.createElement('div');
let dateText: string;
const startTs = param.value[2];
const endTs = param.value[3];
if (this.settings.tooltipDateInterval && startTs && endTs && (endTs - 1) > startTs) {
const startDateText = this.tooltipDateFormat.update(startTs, interval);
const endDateText = this.tooltipDateFormat.update(endTs - 1, interval);
if (startDateText === endDateText) {
dateText = startDateText;
} else {
dateText = startDateText + ' - ' + endDateText;
}
} else {
const ts = param.value[0];
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);
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');
let formatFunction = this.valueFormatFunction;
let latestData: FormattedData;
let units = '';
let decimals = 0;
if (item.dataItem) {
if (item.dataItem.tooltipValueFormatFunction) {
formatFunction = item.dataItem.tooltipValueFormatFunction;
}
latestData = item.dataItem.latestData;
units = item.dataItem.units;
decimals = item.dataItem.decimals;
}
if (!latestData) {
latestData = {} as FormattedData;
}
const value = formatFunction(item.param.value[1], latestData, units, decimals);
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;
}
private static mapTooltipParams(params: CallbackDataParams[] | CallbackDataParams,
series?: TimeSeriesChartDataItem[],
focusedSeriesIndex?: number): TooltipParams {
const result: TooltipParams = {
items: [],
comparisonItems: []
};
if (!params || Array.isArray(params) && !params[0]) {
return result;
}
const firstParam = Array.isArray(params) ? params[0] : params;
if (!firstParam.value) {
return result;
}
let seriesParams: CallbackDataParams = null;
if (Array.isArray(params) && focusedSeriesIndex > -1) {
seriesParams = params.find(param => param.seriesIndex === focusedSeriesIndex);
} else if (!Array.isArray(params)) {
seriesParams = params;
}
if (seriesParams) {
TimeSeriesChartTooltip.appendTooltipItem(result, seriesParams, series);
} else if (Array.isArray(params)) {
for (seriesParams of params) {
TimeSeriesChartTooltip.appendTooltipItem(result, seriesParams, series);
}
}
return result;
}
private static appendTooltipItem(tooltipParams: TooltipParams, seriesParams: CallbackDataParams, series?: TimeSeriesChartDataItem[]) {
const dataItem = series?.find(s => s.id === seriesParams.seriesId);
const tooltipItem: TooltipItem = {
param: seriesParams,
dataItem
};
if (dataItem?.comparisonItem) {
tooltipParams.comparisonItems.push(tooltipItem);
} else {
tooltipParams.items.push(tooltipItem);
}
};
}

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

@ -74,20 +74,20 @@
<th>
<ng-container *ngTemplateOutlet="legendItem; context:{legendKey: legendKey, left: true}"></ng-container>
</th>
<td *ngIf="legendConfig.showMin === true" class="tb-time-series-chart-legend-value" [style]="legendValueStyle">
{{ legendData.data[legendKey.dataIndex].min }}
<td *ngIf="legendConfig.showMin === true" class="tb-time-series-chart-legend-value" [style]="legendValueStyle"
[innerHTML]="legendData.data[legendKey.dataIndex].min | safe: 'html'">
</td>
<td *ngIf="legendConfig.showMax === true" class="tb-time-series-chart-legend-value" [style]="legendValueStyle">
{{ legendData.data[legendKey.dataIndex].max }}
<td *ngIf="legendConfig.showMax === true" class="tb-time-series-chart-legend-value" [style]="legendValueStyle"
[innerHTML]="legendData.data[legendKey.dataIndex].max | safe: 'html'">
</td>
<td *ngIf="legendConfig.showAvg === true" class="tb-time-series-chart-legend-value" [style]="legendValueStyle">
{{ legendData.data[legendKey.dataIndex].avg }}
<td *ngIf="legendConfig.showAvg === true" class="tb-time-series-chart-legend-value" [style]="legendValueStyle"
[innerHTML]="legendData.data[legendKey.dataIndex].avg | safe: 'html'">
</td>
<td *ngIf="legendConfig.showTotal === true" class="tb-time-series-chart-legend-value" [style]="legendValueStyle">
{{ legendData.data[legendKey.dataIndex].total }}
<td *ngIf="legendConfig.showTotal === true" class="tb-time-series-chart-legend-value" [style]="legendValueStyle"
[innerHTML]="legendData.data[legendKey.dataIndex].total | safe: 'html'">
</td>
<td *ngIf="legendConfig.showLatest === true" class="tb-time-series-chart-legend-value" [style]="legendValueStyle">
{{ legendData.data[legendKey.dataIndex].latest }}
<td *ngIf="legendConfig.showLatest === true" class="tb-time-series-chart-legend-value" [style]="legendValueStyle"
[innerHTML]="legendData.data[legendKey.dataIndex].latest | safe: 'html'">
</td>
</tr>
</tbody>
@ -106,15 +106,15 @@
(mouseleave)="onLegendKeyLeave(legendKey)"
(click)="toggleLegendKey(legendKey)">
<div class="tb-time-series-chart-legend-item-label-circle" [style]="{background: !legendKey.dataKey.hidden ? legendKey.dataKey.color : null}"></div>
<div [style]="!legendKey.dataKey.hidden ? legendLabelStyle : disabledLegendLabelStyle">{{ legendKey.dataKey.label }}</div>
<div [style]="!legendKey.dataKey.hidden ? legendLabelStyle : disabledLegendLabelStyle" [innerHTML]="legendKey.dataKey.label | safe: 'html'"></div>
</div>
</div>
</ng-template>
<ng-template #legendDataRow let-label="label" let-type="type">
<tr>
<th class="tb-time-series-chart-legend-type-label" [style]="legendColumnTitleStyle">{{ label | translate }}</th>
<td *ngFor="let legendKey of legendKeys" class="tb-time-series-chart-legend-value" [style]="legendValueStyle">
{{ legendData.data[legendKey.dataIndex][type] }}
<td *ngFor="let legendKey of legendKeys" class="tb-time-series-chart-legend-value" [style]="legendValueStyle"
[innerHTML]="legendData.data[legendKey.dataIndex][type] | safe: 'html'">
</td>
</tr>
</ng-template>

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

@ -23,15 +23,12 @@ import {
autoDateFormat,
AutoDateFormatSettings,
ComponentStyle,
DateFormatProcessor,
DateFormatSettings,
Font,
tsToFormatTimeUnit,
ValueSourceConfig,
ValueSourceType
} from '@shared/models/widget-settings.models';
import {
CallbackDataParams,
TimeAxisBandWidthCalculator,
VisualMapComponentOption,
XAXisOption,
@ -75,12 +72,10 @@ import { BuiltinTextPosition } from 'zrender/src/core/types';
import { CartesianAxisOption } from 'echarts/types/src/coord/cartesian/AxisModel';
import {
calculateAggIntervalWithWidgetTimeWindow,
Interval,
IntervalMath,
WidgetTimewindow
} from '@shared/models/time/time.models';
import { UtilsService } from '@core/services/utils.service';
import { Renderer2 } from '@angular/core';
import {
chartAnimationDefaultSettings,
ChartAnimationSettings,
@ -98,6 +93,11 @@ import {
prepareChartThemeColor
} from '@home/components/widget/lib/chart/chart.models';
import { BarSeriesLabelOption } from 'echarts/types/src/chart/bar/BarSeries';
import {
TimeSeriesChartTooltipTrigger,
TimeSeriesChartTooltipValueFormatFunction,
TimeSeriesChartTooltipWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-tooltip.models';
type TimeSeriesChartDataEntry = [number, any, number, number];
@ -127,9 +127,6 @@ const toTimeSeriesChartDataEntry = (entry: DataEntry, valueConverter?: (value: a
return item;
};
export type TimeSeriesChartTooltipValueFormatFunction =
(value: any, latestData: FormattedData, units?: string, decimals?: number) => string;
export interface TimeSeriesChartDataItem {
id: string;
datasource: Datasource;
@ -216,245 +213,6 @@ export const adjustTimeAxisExtentToData = (timeAxisOption: TimeAxisBaseOption,
timeAxisOption.max = (typeof max !== 'undefined' && Math.abs(max - defaultMax) < 1000) ? max : defaultMax;
};
export enum TimeSeriesChartTooltipTrigger {
point = 'point',
axis = 'axis'
}
export const tooltipTriggerTranslationMap = new Map<TimeSeriesChartTooltipTrigger, string>(
[
[ TimeSeriesChartTooltipTrigger.point, 'tooltip.trigger-point' ],
[ TimeSeriesChartTooltipTrigger.axis, 'tooltip.trigger-axis' ]
]
);
export interface TimeSeriesChartTooltipWidgetSettings {
showTooltip: boolean;
tooltipTrigger?: TimeSeriesChartTooltipTrigger;
tooltipShowFocusedSeries?: boolean;
tooltipLabelFont: Font;
tooltipLabelColor: string;
tooltipValueFont: Font;
tooltipValueColor: string;
tooltipValueFormatter?: string | TimeSeriesChartTooltipValueFormatFunction;
tooltipShowDate: boolean;
tooltipDateInterval?: boolean;
tooltipDateFormat: DateFormatSettings;
tooltipDateFont: Font;
tooltipDateColor: string;
tooltipBackgroundColor: string;
tooltipBackgroundBlur: number;
}
export const createTooltipValueFormatFunction =
(tooltipValueFormatter: string | TimeSeriesChartTooltipValueFormatFunction): TimeSeriesChartTooltipValueFormatFunction => {
let tooltipValueFormatFunction: TimeSeriesChartTooltipValueFormatFunction;
if (isFunction(tooltipValueFormatter)) {
tooltipValueFormatFunction = tooltipValueFormatter as TimeSeriesChartTooltipValueFormatFunction;
} else if (typeof tooltipValueFormatter === 'string' && tooltipValueFormatter.length) {
try {
tooltipValueFormatFunction =
new Function('value', 'latestData', tooltipValueFormatter) as TimeSeriesChartTooltipValueFormatFunction;
} catch (e) {}
}
return tooltipValueFormatFunction;
};
export const timeSeriesChartTooltipFormatter = (renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: TimeSeriesChartTooltipWidgetSettings,
params: CallbackDataParams[] | CallbackDataParams,
valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction,
focusedSeriesIndex: number,
series?: TimeSeriesChartDataItem[],
interval?: Interval): null | HTMLElement => {
const tooltipParams = mapTooltipParams(params, series, focusedSeriesIndex);
if (!tooltipParams.items.length && !tooltipParams.comparisonItems.length) {
return null;
}
const tooltipElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(tooltipElement, 'display', 'flex');
renderer.setStyle(tooltipElement, 'flex-direction', 'column');
renderer.setStyle(tooltipElement, 'align-items', 'flex-start');
renderer.setStyle(tooltipElement, 'gap', '16px');
buildItemsTooltip(tooltipElement, tooltipParams.items, renderer, tooltipDateFormat, settings, valueFormatFunction, interval);
buildItemsTooltip(tooltipElement, tooltipParams.comparisonItems, renderer, tooltipDateFormat, settings, valueFormatFunction, interval);
return tooltipElement;
};
interface TooltipItem {
param: CallbackDataParams;
dataItem: TimeSeriesChartDataItem;
}
interface TooltipParams {
items: TooltipItem[];
comparisonItems: TooltipItem[];
}
const buildItemsTooltip = (tooltipElement: HTMLElement,
items: TooltipItem[],
renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: TimeSeriesChartTooltipWidgetSettings,
valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction,
interval?: Interval) => {
if (items.length) {
const tooltipItemsElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(tooltipItemsElement, 'display', 'flex');
renderer.setStyle(tooltipItemsElement, 'flex-direction', 'column');
renderer.setStyle(tooltipItemsElement, 'align-items', 'flex-start');
renderer.setStyle(tooltipItemsElement, 'gap', '4px');
renderer.appendChild(tooltipElement, tooltipItemsElement);
if (settings.tooltipShowDate) {
renderer.appendChild(tooltipItemsElement,
constructTooltipDateElement(renderer, tooltipDateFormat, settings, items[0].param, interval));
}
for (const item of items) {
renderer.appendChild(tooltipItemsElement,
constructTooltipSeriesElement(renderer, settings, item, valueFormatFunction));
}
}
};
const mapTooltipParams = (params: CallbackDataParams[] | CallbackDataParams,
series?: TimeSeriesChartDataItem[],
focusedSeriesIndex?: number): TooltipParams => {
const result: TooltipParams = {
items: [],
comparisonItems: []
};
if (!params || Array.isArray(params) && !params[0]) {
return result;
}
const firstParam = Array.isArray(params) ? params[0] : params;
if (!firstParam.value) {
return result;
}
let seriesParams: CallbackDataParams = null;
if (Array.isArray(params) && focusedSeriesIndex > -1) {
seriesParams = params.find(param => param.seriesIndex === focusedSeriesIndex);
} else if (!Array.isArray(params)) {
seriesParams = params;
}
if (seriesParams) {
appendTooltipItem(result, seriesParams, series);
} else if (Array.isArray(params)) {
for (seriesParams of params) {
appendTooltipItem(result, seriesParams, series);
}
}
return result;
};
const appendTooltipItem = (tooltipParams: TooltipParams, seriesParams: CallbackDataParams, series?: TimeSeriesChartDataItem[]) => {
const dataItem = series?.find(s => s.id === seriesParams.seriesId);
const tooltipItem: TooltipItem = {
param: seriesParams,
dataItem
};
if (dataItem?.comparisonItem) {
tooltipParams.comparisonItems.push(tooltipItem);
} else {
tooltipParams.items.push(tooltipItem);
}
};
const constructTooltipDateElement = (renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: TimeSeriesChartTooltipWidgetSettings,
param: CallbackDataParams,
interval?: Interval): HTMLElement => {
const dateElement: HTMLElement = renderer.createElement('div');
let dateText: string;
const startTs = param.value[2];
const endTs = param.value[3];
if (settings.tooltipDateInterval && startTs && endTs && (endTs - 1) > startTs) {
const startDateText = tooltipDateFormat.update(startTs, interval);
const endDateText = tooltipDateFormat.update(endTs - 1, interval);
if (startDateText === endDateText) {
dateText = startDateText;
} else {
dateText = startDateText + ' - ' + endDateText;
}
} else {
const ts = param.value[0];
dateText = tooltipDateFormat.update(ts, interval);
}
renderer.appendChild(dateElement, renderer.createText(dateText));
renderer.setStyle(dateElement, 'font-family', settings.tooltipDateFont.family);
renderer.setStyle(dateElement, 'font-size', settings.tooltipDateFont.size + settings.tooltipDateFont.sizeUnit);
renderer.setStyle(dateElement, 'font-style', settings.tooltipDateFont.style);
renderer.setStyle(dateElement, 'font-weight', settings.tooltipDateFont.weight);
renderer.setStyle(dateElement, 'line-height', settings.tooltipDateFont.lineHeight);
renderer.setStyle(dateElement, 'color', settings.tooltipDateColor);
return dateElement;
};
const constructTooltipSeriesElement = (renderer: Renderer2,
settings: TimeSeriesChartTooltipWidgetSettings,
item: TooltipItem,
valueFormatFunction: TimeSeriesChartTooltipValueFormatFunction): HTMLElement => {
const labelValueElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(labelValueElement, 'display', 'flex');
renderer.setStyle(labelValueElement, 'flex-direction', 'row');
renderer.setStyle(labelValueElement, 'align-items', 'center');
renderer.setStyle(labelValueElement, 'align-self', 'stretch');
renderer.setStyle(labelValueElement, 'gap', '12px');
const labelElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(labelElement, 'display', 'flex');
renderer.setStyle(labelElement, 'align-items', 'center');
renderer.setStyle(labelElement, 'gap', '8px');
renderer.appendChild(labelValueElement, labelElement);
const circleElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(circleElement, 'width', '8px');
renderer.setStyle(circleElement, 'height', '8px');
renderer.setStyle(circleElement, 'border-radius', '50%');
renderer.setStyle(circleElement, 'background', item.param.color);
renderer.appendChild(labelElement, circleElement);
const labelTextElement: HTMLElement = renderer.createElement('div');
renderer.appendChild(labelTextElement, renderer.createText(item.param.seriesName));
renderer.setStyle(labelTextElement, 'font-family', settings.tooltipLabelFont.family);
renderer.setStyle(labelTextElement, 'font-size', settings.tooltipLabelFont.size + settings.tooltipLabelFont.sizeUnit);
renderer.setStyle(labelTextElement, 'font-style', settings.tooltipLabelFont.style);
renderer.setStyle(labelTextElement, 'font-weight', settings.tooltipLabelFont.weight);
renderer.setStyle(labelTextElement, 'line-height', settings.tooltipLabelFont.lineHeight);
renderer.setStyle(labelTextElement, 'color', settings.tooltipLabelColor);
renderer.appendChild(labelElement, labelTextElement);
const valueElement: HTMLElement = renderer.createElement('div');
let formatFunction = valueFormatFunction;
let latestData: FormattedData;
let units = '';
let decimals = 0;
if (item.dataItem) {
if (item.dataItem.tooltipValueFormatFunction) {
formatFunction = item.dataItem.tooltipValueFormatFunction;
}
latestData = item.dataItem.latestData;
units = item.dataItem.units;
decimals = item.dataItem.decimals;
}
if (!latestData) {
latestData = {} as FormattedData;
}
const value = formatFunction(item.param.value[1], latestData, units, decimals);
renderer.appendChild(valueElement, renderer.createText(value));
renderer.setStyle(valueElement, 'flex', '1');
renderer.setStyle(valueElement, 'text-align', 'end');
renderer.setStyle(valueElement, 'font-family', settings.tooltipValueFont.family);
renderer.setStyle(valueElement, 'font-size', settings.tooltipValueFont.size + settings.tooltipValueFont.sizeUnit);
renderer.setStyle(valueElement, 'font-style', settings.tooltipValueFont.style);
renderer.setStyle(valueElement, 'font-weight', settings.tooltipValueFont.weight);
renderer.setStyle(valueElement, 'line-height', settings.tooltipValueFont.lineHeight);
renderer.setStyle(valueElement, 'color', settings.tooltipValueColor);
renderer.appendChild(labelValueElement, valueElement);
return labelValueElement;
};
export enum TimeSeriesChartType {
default = 'default',

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

@ -21,7 +21,6 @@ import {
createTimeSeriesVisualMapOption,
createTimeSeriesXAxis,
createTimeSeriesYAxis,
createTooltipValueFormatFunction,
defaultTimeSeriesChartYAxisSettings,
generateChartData,
LineSeriesStepType,
@ -37,9 +36,6 @@ import {
TimeSeriesChartThreshold,
timeSeriesChartThresholdDefaultSettings,
TimeSeriesChartThresholdItem,
timeSeriesChartTooltipFormatter,
TimeSeriesChartTooltipTrigger,
TimeSeriesChartTooltipValueFormatFunction,
TimeSeriesChartType,
TimeSeriesChartXAxis,
TimeSeriesChartYAxis,
@ -74,6 +70,12 @@ import { DeepPartial } from '@shared/models/common';
import { BarRenderSharedContext } from '@home/components/widget/lib/chart/time-series-chart-bar.models';
import { TimeSeriesChartStateValueConverter } from '@home/components/widget/lib/chart/time-series-chart-state.models';
import { ChartLabelPosition, ChartShape, toAnimationOption } from '@home/components/widget/lib/chart/chart.models';
import {
createTooltipValueFormatFunction,
TimeSeriesChartTooltip,
TimeSeriesChartTooltipTrigger,
TimeSeriesChartTooltipValueFormatFunction
} from '@home/components/widget/lib/chart/time-series-chart-tooltip.models';
export class TbTimeSeriesChart {
@ -133,7 +135,7 @@ export class TbTimeSeriesChart {
private timeSeriesChartOptions: EChartsOption;
private readonly tooltipDateFormat: DateFormatProcessor;
private readonly tooltipValueFormatFunction: TimeSeriesChartTooltipValueFormatFunction;
private readonly timeSeriesChartTooltip: TimeSeriesChartTooltip;
private readonly stateValueConverter: TimeSeriesChartStateValueConverter;
private yMinSubject = new BehaviorSubject(-1);
@ -162,6 +164,8 @@ export class TbTimeSeriesChart {
private renderer: Renderer2,
private autoResize = true) {
let tooltipValueFormatFunction: TimeSeriesChartTooltipValueFormatFunction;
this.settings = mergeDeep({} as TimeSeriesChartSettings,
timeSeriesChartDefaultSettings,
this.inputSettings as TimeSeriesChartSettings);
@ -169,7 +173,7 @@ export class TbTimeSeriesChart {
this.stackMode = !this.comparisonEnabled && this.settings.stack;
if (this.settings.states && this.settings.states.length) {
this.stateValueConverter = new TimeSeriesChartStateValueConverter(this.ctx.utilsService, this.settings.states);
this.tooltipValueFormatFunction = this.stateValueConverter.tooltipFormatter;
tooltipValueFormatFunction = this.stateValueConverter.tooltipFormatter;
}
const $dashboardPageElement = this.ctx.$containerParent.parents('.tb-dashboard-page');
const dashboardPageElement = $dashboardPageElement.length ? $($dashboardPageElement[$dashboardPageElement.length-1]) : null;
@ -183,14 +187,20 @@ export class TbTimeSeriesChart {
if (this.settings.tooltipShowDate) {
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat);
}
if (!this.tooltipValueFormatFunction) {
this.tooltipValueFormatFunction =
createTooltipValueFormatFunction(this.settings.tooltipValueFormatter);
if (!this.tooltipValueFormatFunction) {
this.tooltipValueFormatFunction = (value, _latestData, units, decimals) => formatValue(value, decimals, units, false);
if (!tooltipValueFormatFunction) {
tooltipValueFormatFunction = createTooltipValueFormatFunction(this.settings.tooltipValueFormatter);
if (!tooltipValueFormatFunction) {
tooltipValueFormatFunction = (value, _latestData, units, decimals) => formatValue(value, decimals, units, false);
}
}
}
this.timeSeriesChartTooltip = new TimeSeriesChartTooltip(
this.renderer,
this.ctx.sanitizer,
this.settings,
this.tooltipDateFormat,
tooltipValueFormatFunction
);
this.onResize();
if (this.autoResize) {
this.shapeResize$ = new ResizeObserver(() => {
@ -603,10 +613,12 @@ export class TbTimeSeriesChart {
type: this.noAggregation ? 'line' : 'shadow'
},
formatter: (params: CallbackDataParams[]) =>
this.settings.showTooltip ? timeSeriesChartTooltipFormatter(this.renderer, this.tooltipDateFormat,
this.settings, params, this.tooltipValueFormatFunction,
this.timeSeriesChartTooltip.formatted(
params,
this.settings.tooltipShowFocusedSeries ? getFocusedSeriesIndex(this.timeSeriesChart) : -1,
this.dataItems, this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined,
this.dataItems,
this.noAggregation ? null : this.ctx.timeWindow.interval,
),
padding: [8, 12],
backgroundColor: this.settings.tooltipBackgroundColor,
borderWidth: 0,

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

@ -33,7 +33,6 @@ import {
TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import {
TimeSeriesChartTooltipTrigger,
TimeSeriesChartKeySettings,
TimeSeriesChartType,
TimeSeriesChartYAxes,
@ -41,6 +40,7 @@ import {
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { WidgetService } from '@core/http/widget.service';
import { TimeSeriesChartTooltipTrigger } from '@home/components/widget/lib/chart/time-series-chart-tooltip.models';
@Component({
selector: 'tb-time-series-chart-widget-settings',

Loading…
Cancel
Save