Browse Source

UI: Implement comparison support for new time series charts.

pull/10562/head
Igor Kulikov 3 years ago
parent
commit
3e527f2634
  1. 9
      ui-ngx/src/app/core/api/widget-api.models.ts
  2. 14
      ui-ngx/src/app/core/api/widget-subscription.ts
  3. 9
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts
  4. 219
      ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts
  5. 198
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  6. 142
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  7. 12
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts
  8. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts
  9. 16
      ui-ngx/src/app/shared/models/widget.models.ts

9
ui-ngx/src/app/core/api/widget-api.models.ts

@ -30,7 +30,12 @@ import {
import { TimeService } from '../services/time.service'; import { TimeService } from '../services/time.service';
import { DeviceService } from '../http/device.service'; import { DeviceService } from '../http/device.service';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { SubscriptionTimewindow, Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; import {
ComparisonDuration,
SubscriptionTimewindow,
Timewindow,
WidgetTimewindow
} from '@shared/models/time/time.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { RafService } from '@core/services/raf.service'; import { RafService } from '@core/services/raf.service';
@ -265,7 +270,7 @@ export interface WidgetSubscriptionOptions {
onTimewindowChangeFunction?: (timewindow: Timewindow) => Timewindow; onTimewindowChangeFunction?: (timewindow: Timewindow) => Timewindow;
legendConfig?: LegendConfig; legendConfig?: LegendConfig;
comparisonEnabled?: boolean; comparisonEnabled?: boolean;
timeForComparison?: moment_.unitOfTime.DurationConstructor; timeForComparison?: ComparisonDuration;
comparisonCustomIntervalValue?: number; comparisonCustomIntervalValue?: number;
decimals?: number; decimals?: number;
units?: string; units?: string;

14
ui-ngx/src/app/core/api/widget-subscription.ts

@ -23,13 +23,13 @@ import {
WidgetSubscriptionOptions WidgetSubscriptionOptions
} from '@core/api/widget-api.models'; } from '@core/api/widget-api.models';
import { import {
DataKey, DataKey, DataKeySettingsWithComparison,
DataSet, DataSet,
DataSetHolder, DataSetHolder,
Datasource, Datasource,
DatasourceData, DatasourceData,
datasourcesHasAggregation, datasourcesHasAggregation,
DatasourceType, DatasourceType, isDataKeySettingsWithComparison,
LegendConfig, LegendConfig,
LegendData, LegendData,
LegendKey, LegendKey,
@ -513,7 +513,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.configuredDatasources.forEach((datasource, datasourceIndex) => { this.configuredDatasources.forEach((datasource, datasourceIndex) => {
const additionalDataKeys: DataKey[] = []; const additionalDataKeys: DataKey[] = [];
datasource.dataKeys.forEach((dataKey, dataKeyIndex) => { datasource.dataKeys.forEach((dataKey, dataKeyIndex) => {
if (dataKey.settings.comparisonSettings && dataKey.settings.comparisonSettings.showValuesForComparison) { if (isDataKeySettingsWithComparison(dataKey.settings) && dataKey.settings.comparisonSettings.showValuesForComparison) {
const additionalDataKey = deepClone(dataKey); const additionalDataKey = deepClone(dataKey);
additionalDataKey.isAdditional = true; additionalDataKey.isAdditional = true;
additionalDataKey.origDataKeyIndex = dataKeyIndex; additionalDataKey.origDataKeyIndex = dataKeyIndex;
@ -1468,11 +1468,12 @@ export class WidgetSubscription implements IWidgetSubscription {
if (datasource.isAdditional) { if (datasource.isAdditional) {
const origDatasource = this.datasourcePages[datasource.origDatasourceIndex].data[dIndex]; const origDatasource = this.datasourcePages[datasource.origDatasourceIndex].data[dIndex];
datasource.dataKeys.forEach((dataKey) => { datasource.dataKeys.forEach((dataKey) => {
if (dataKey.settings.comparisonSettings.color) { const settings: DataKeySettingsWithComparison = dataKey.settings;
if (settings.comparisonSettings.color) {
dataKey.color = dataKey.settings.comparisonSettings.color; dataKey.color = dataKey.settings.comparisonSettings.color;
} }
const origDataKey = origDatasource.dataKeys[dataKey.origDataKeyIndex]; const origDataKey = origDatasource.dataKeys[dataKey.origDataKeyIndex];
origDataKey.settings.comparisonSettings.color = dataKey.color; (origDataKey.settings as DataKeySettingsWithComparison).comparisonSettings.color = dataKey.color;
}); });
} }
}); });
@ -1523,7 +1524,8 @@ export class WidgetSubscription implements IWidgetSubscription {
const formattedData = flatFormattedData(formattedDataArray); const formattedData = flatFormattedData(formattedDataArray);
datasource.dataKeys.forEach((dataKey) => { datasource.dataKeys.forEach((dataKey) => {
if (this.comparisonEnabled && dataKey.isAdditional && dataKey.settings.comparisonSettings.comparisonValuesLabel) { if (this.comparisonEnabled && dataKey.isAdditional && isDataKeySettingsWithComparison(dataKey.settings) &&
dataKey.settings.comparisonSettings.comparisonValuesLabel) {
dataKey.label = createLabelFromPattern(dataKey.settings.comparisonSettings.comparisonValuesLabel, formattedData); dataKey.label = createLabelFromPattern(dataKey.settings.comparisonSettings.comparisonValuesLabel, formattedData);
} else { } else {
if (this.comparisonEnabled && dataKey.isAdditional) { if (this.comparisonEnabled && dataKey.isAdditional) {

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

@ -131,6 +131,11 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
yAxes: [settings.yAxes, []], yAxes: [settings.yAxes, []],
series: [this.getSeries(configData.config.datasources), []], series: [this.getSeries(configData.config.datasources), []],
comparisonEnabled: [settings.comparisonEnabled, []],
timeForComparison: [settings.timeForComparison, []],
comparisonCustomIntervalValue: [settings.comparisonCustomIntervalValue, [Validators.min(0)]],
thresholds: [settings.thresholds, []], thresholds: [settings.thresholds, []],
showTitle: [configData.config.showTitle, []], showTitle: [configData.config.showTitle, []],
@ -201,6 +206,10 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.comparisonEnabled = config.comparisonEnabled;
this.widgetConfig.config.settings.timeForComparison = config.timeForComparison;
this.widgetConfig.config.settings.comparisonCustomIntervalValue = config.comparisonCustomIntervalValue;
this.widgetConfig.config.settings.thresholds = config.thresholds; this.widgetConfig.config.settings.thresholds = config.thresholds;
this.widgetConfig.config.settings.dataZoom = config.dataZoom; this.widgetConfig.config.settings.dataZoom = config.dataZoom;

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

@ -115,6 +115,7 @@ export type EChartsSeriesItem = {
decimals?: number; decimals?: number;
latestData?: FormattedData; latestData?: FormattedData;
tooltipValueFormatFunction?: EChartsTooltipValueFormatFunction; tooltipValueFormatFunction?: EChartsTooltipValueFormatFunction;
comparisonItem?: boolean;
}; };
export enum EChartsShape { export enum EChartsShape {
@ -200,9 +201,9 @@ export const timeAxisBandWidthCalculator: TimeAxisBandWidthCalculator = (model)
} }
}; };
export const getXAxis = (chart: ECharts): Axis2D => { export const getAxis = (chart: ECharts, mainType: string, axisId: string): Axis2D => {
const model: GlobalModel = (chart as any).getModel(); const model: GlobalModel = (chart as any).getModel();
const models = model.queryComponents({mainType: 'xAxis'}); const models = model.queryComponents({mainType, id: axisId});
if (models?.length) { if (models?.length) {
const axisModel = models[0] as AxisModel; const axisModel = models[0] as AxisModel;
return axisModel.axis; return axisModel.axis;
@ -210,27 +211,20 @@ export const getXAxis = (chart: ECharts): Axis2D => {
return null; return null;
}; };
export const getYAxis = (chart: ECharts, axisId: string): Axis2D => { export const calculateAxisSize = (chart: ECharts, mainType: string, axisId: string): number => {
const model: GlobalModel = (chart as any).getModel(); const axis = getAxis(chart, mainType, axisId);
const models = model.queryComponents({mainType: 'yAxis', id: axisId}); return _calculateAxisSize(axis);
if (models?.length) {
const axisModel = models[0] as AxisModel;
return axisModel.axis;
}
return null;
};
export const calculateYAxisWidth = (chart: ECharts, axisId: string): number => {
const axis = getYAxis(chart, axisId);
return calculateAxisSize(axis);
}; };
export const calculateXAxisHeight = (chart: ECharts): number => { export const measureAxisNameSize = (chart: ECharts, mainType: string, axisId: string, name: string): number => {
const axis = getXAxis(chart); const axis = getAxis(chart, mainType, axisId);
return calculateAxisSize(axis); if (axis) {
return axis.model.getModel('nameTextStyle').getTextRect(name).height;
}
return 0;
}; };
const calculateAxisSize = (axis: Axis2D): number => { const _calculateAxisSize = (axis: Axis2D): number => {
let size = 0; let size = 0;
if (axis && axis.model.option.show) { if (axis && axis.model.option.show) {
const labelUnionRect = estimateLabelUnionRect(axis); const labelUnionRect = estimateLabelUnionRect(axis);
@ -247,22 +241,6 @@ const calculateAxisSize = (axis: Axis2D): number => {
return size; return size;
}; };
export const measureYAxisNameWidth = (chart: ECharts, axisId: string, name: string): number => {
const axis = getYAxis(chart, axisId);
if (axis) {
return axis.model.getModel('nameTextStyle').getTextRect(name).height;
}
return 0;
};
export const measureXAxisNameHeight = (chart: ECharts, name: string): number => {
const axis = getXAxis(chart);
if (axis) {
return axis.model.getModel('nameTextStyle').getTextRect(name).height;
}
return 0;
};
const measureSymbolOffset = (symbol: string, symbolSize: any): number => { const measureSymbolOffset = (symbol: string, symbolSize: any): number => {
if (isNumber(symbolSize)) { if (isNumber(symbolSize)) {
if (symbol) { if (symbol) {
@ -280,7 +258,7 @@ const measureSymbolOffset = (symbol: string, symbolSize: any): number => {
export const measureThresholdOffset = (chart: ECharts, axisId: string, thresholdId: string, value: any): [number, number] => { export const measureThresholdOffset = (chart: ECharts, axisId: string, thresholdId: string, value: any): [number, number] => {
const offset: [number, number] = [0,0]; const offset: [number, number] = [0,0];
const axis = getYAxis(chart, axisId); const axis = getAxis(chart, 'yAxis', axisId);
if (axis && !axis.scale.isBlank()) { if (axis && !axis.scale.isBlank()) {
const extent = axis.scale.getExtent(); const extent = axis.scale.getExtent();
const model: GlobalModel = (chart as any).getModel(); const model: GlobalModel = (chart as any).getModel();
@ -352,7 +330,7 @@ export const measureThresholdOffset = (chart: ECharts, axisId: string, threshold
}; };
export const getAxisExtent = (chart: ECharts, axisId: string): [number, number] => { export const getAxisExtent = (chart: ECharts, axisId: string): [number, number] => {
const axis = getYAxis(chart, axisId); const axis = getAxis(chart, 'yAxis', axisId);
if (axis) { if (axis) {
return axis.scale.getExtent(); return axis.scale.getExtent();
} }
@ -503,43 +481,72 @@ export const echartsTooltipFormatter = (renderer: Renderer2,
focusedSeriesIndex: number, focusedSeriesIndex: number,
series?: EChartsSeriesItem[], series?: EChartsSeriesItem[],
interval?: Interval): null | HTMLElement => { interval?: Interval): null | HTMLElement => {
if (!params || Array.isArray(params) && !params[0]) {
return null; const tooltipParams = mapTooltipParams(params, series, focusedSeriesIndex);
} if (!tooltipParams.items.length && !tooltipParams.comparisonItems.length) {
const firstParam = Array.isArray(params) ? params[0] : params;
if (!firstParam.value) {
return null; return null;
} }
const tooltipElement: HTMLElement = renderer.createElement('div'); const tooltipElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(tooltipElement, 'display', 'flex'); renderer.setStyle(tooltipElement, 'display', 'flex');
renderer.setStyle(tooltipElement, 'flex-direction', 'column'); renderer.setStyle(tooltipElement, 'flex-direction', 'column');
renderer.setStyle(tooltipElement, 'align-items', 'flex-start'); renderer.setStyle(tooltipElement, 'align-items', 'flex-start');
renderer.setStyle(tooltipElement, 'gap', '4px'); renderer.setStyle(tooltipElement, 'gap', '16px');
if (settings.tooltipShowDate) {
const dateElement: HTMLElement = renderer.createElement('div'); buildItemsTooltip(tooltipElement, tooltipParams.items, renderer, tooltipDateFormat, settings, valueFormatFunction, interval);
let dateText: string; buildItemsTooltip(tooltipElement, tooltipParams.comparisonItems, renderer, tooltipDateFormat, settings, valueFormatFunction, interval);
const startTs = firstParam.value[2];
const endTs = firstParam.value[3]; return tooltipElement;
if (settings.tooltipDateInterval && startTs && endTs && (endTs - 1) > startTs) { };
const startDateText = tooltipDateFormat.update(startTs, interval);
const endDateText = tooltipDateFormat.update(endTs - 1, interval); interface TooltipItem {
if (startDateText === endDateText) { param: CallbackDataParams;
dateText = startDateText; dataItem: EChartsSeriesItem;
} else { }
dateText = startDateText + ' - ' + endDateText;
} interface TooltipParams {
} else { items: TooltipItem[];
const ts = firstParam.value[0]; comparisonItems: TooltipItem[];
dateText = tooltipDateFormat.update(ts, interval); }
const buildItemsTooltip = (tooltipElement: HTMLElement,
items: TooltipItem[],
renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: EChartsTooltipWidgetSettings,
valueFormatFunction: EChartsTooltipValueFormatFunction,
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,
constructEchartsTooltipDateElement(renderer, tooltipDateFormat, settings, items[0].param, interval));
}
for (const item of items) {
renderer.appendChild(tooltipItemsElement,
constructEchartsTooltipSeriesElement(renderer, settings, item, valueFormatFunction));
} }
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); const mapTooltipParams = (params: CallbackDataParams[] | CallbackDataParams,
renderer.setStyle(dateElement, 'font-weight', settings.tooltipDateFont.weight); series?: EChartsSeriesItem[],
renderer.setStyle(dateElement, 'line-height', settings.tooltipDateFont.lineHeight); focusedSeriesIndex?: number): TooltipParams => {
renderer.setStyle(dateElement, 'color', settings.tooltipDateColor); const result: TooltipParams = {
renderer.appendChild(tooltipElement, dateElement); 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; let seriesParams: CallbackDataParams = null;
if (Array.isArray(params) && focusedSeriesIndex > -1) { if (Array.isArray(params) && focusedSeriesIndex > -1) {
@ -548,22 +555,63 @@ export const echartsTooltipFormatter = (renderer: Renderer2,
seriesParams = params; seriesParams = params;
} }
if (seriesParams) { if (seriesParams) {
renderer.appendChild(tooltipElement, appendTooltipItem(result, seriesParams, series);
constructEchartsTooltipSeriesElement(renderer, settings, seriesParams, valueFormatFunction, series));
} else if (Array.isArray(params)) { } else if (Array.isArray(params)) {
for (seriesParams of params) { for (seriesParams of params) {
renderer.appendChild(tooltipElement, appendTooltipItem(result, seriesParams, series);
constructEchartsTooltipSeriesElement(renderer, settings, seriesParams, valueFormatFunction, series));
} }
} }
return tooltipElement; return result;
};
const appendTooltipItem = (tooltipParams: TooltipParams, seriesParams: CallbackDataParams, series?: EChartsSeriesItem[]) => {
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 constructEchartsTooltipDateElement = (renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: EChartsTooltipWidgetSettings,
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 constructEchartsTooltipSeriesElement = (renderer: Renderer2, const constructEchartsTooltipSeriesElement = (renderer: Renderer2,
settings: EChartsTooltipWidgetSettings, settings: EChartsTooltipWidgetSettings,
seriesParams: CallbackDataParams, item: TooltipItem,
valueFormatFunction: EChartsTooltipValueFormatFunction, valueFormatFunction: EChartsTooltipValueFormatFunction): HTMLElement => {
series?: EChartsSeriesItem[]): HTMLElement => {
const labelValueElement: HTMLElement = renderer.createElement('div'); const labelValueElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(labelValueElement, 'display', 'flex'); renderer.setStyle(labelValueElement, 'display', 'flex');
renderer.setStyle(labelValueElement, 'flex-direction', 'row'); renderer.setStyle(labelValueElement, 'flex-direction', 'row');
@ -579,10 +627,10 @@ const constructEchartsTooltipSeriesElement = (renderer: Renderer2,
renderer.setStyle(circleElement, 'width', '8px'); renderer.setStyle(circleElement, 'width', '8px');
renderer.setStyle(circleElement, 'height', '8px'); renderer.setStyle(circleElement, 'height', '8px');
renderer.setStyle(circleElement, 'border-radius', '50%'); renderer.setStyle(circleElement, 'border-radius', '50%');
renderer.setStyle(circleElement, 'background', seriesParams.color); renderer.setStyle(circleElement, 'background', item.param.color);
renderer.appendChild(labelElement, circleElement); renderer.appendChild(labelElement, circleElement);
const labelTextElement: HTMLElement = renderer.createElement('div'); const labelTextElement: HTMLElement = renderer.createElement('div');
renderer.appendChild(labelTextElement, renderer.createText(seriesParams.seriesName)); renderer.appendChild(labelTextElement, renderer.createText(item.param.seriesName));
renderer.setStyle(labelTextElement, 'font-family', 'Roboto'); renderer.setStyle(labelTextElement, 'font-family', 'Roboto');
renderer.setStyle(labelTextElement, 'font-size', '12px'); renderer.setStyle(labelTextElement, 'font-size', '12px');
renderer.setStyle(labelTextElement, 'font-style', 'normal'); renderer.setStyle(labelTextElement, 'font-style', 'normal');
@ -596,21 +644,18 @@ const constructEchartsTooltipSeriesElement = (renderer: Renderer2,
let latestData: FormattedData; let latestData: FormattedData;
let units = ''; let units = '';
let decimals = 0; let decimals = 0;
if (series) { if (item.dataItem) {
const item = series.find(s => s.id === seriesParams.seriesId); if (item.dataItem.tooltipValueFormatFunction) {
if (item) { formatFunction = item.dataItem.tooltipValueFormatFunction;
if (item.tooltipValueFormatFunction) {
formatFunction = item.tooltipValueFormatFunction;
}
latestData = item.latestData;
units = item.units;
decimals = item.decimals;
} }
latestData = item.dataItem.latestData;
units = item.dataItem.units;
decimals = item.dataItem.decimals;
} }
if (!latestData) { if (!latestData) {
latestData = {} as FormattedData; latestData = {} as FormattedData;
} }
const value = formatFunction(seriesParams.value[1], latestData, units, decimals); const value = formatFunction(item.param.value[1], latestData, units, decimals);
renderer.appendChild(valueElement, renderer.createText(value)); renderer.appendChild(valueElement, renderer.createText(value));
renderer.setStyle(valueElement, 'flex', '1'); renderer.setStyle(valueElement, 'flex', '1');
renderer.setStyle(valueElement, 'text-align', 'end'); renderer.setStyle(valueElement, 'text-align', 'end');

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

@ -62,13 +62,15 @@ import {
BarVisualSettings, BarVisualSettings,
renderTimeSeriesBar renderTimeSeriesBar
} from '@home/components/widget/lib/chart/time-series-chart-bar.models'; } from '@home/components/widget/lib/chart/time-series-chart-bar.models';
import { DataKey } from '@shared/models/widget.models'; import { DataKey, DataKeySettingsWithComparison, WidgetComparisonSettings } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { TbColorScheme } from '@shared/models/color.models'; import { TbColorScheme } from '@shared/models/color.models';
import { AbstractControl, ValidationErrors } from '@angular/forms'; import { AbstractControl, ValidationErrors } from '@angular/forms';
import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel'; import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { BuiltinTextPosition } from 'zrender/src/core/types'; import { BuiltinTextPosition } from 'zrender/src/core/types';
import { CartesianAxisOption } from 'echarts/types/src/coord/cartesian/AxisModel';
import { WidgetTimewindow } from '@shared/models/time/time.models';
export enum TimeSeriesChartType { export enum TimeSeriesChartType {
default = 'default', default = 'default',
@ -692,7 +694,11 @@ export const timeSeriesChartStateValidator = (control: AbstractControl): Validat
return null; return null;
}; };
export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { export interface TimeSeriesChartComparisonSettings extends WidgetComparisonSettings {
comparisonXAxis?: TimeSeriesChartXAxisSettings;
}
export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings, TimeSeriesChartComparisonSettings {
thresholds: TimeSeriesChartThreshold[]; thresholds: TimeSeriesChartThreshold[];
darkMode: boolean; darkMode: boolean;
dataZoom: boolean; dataZoom: boolean;
@ -750,7 +756,13 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
tooltipDateColor: 'rgba(0, 0, 0, 0.76)', tooltipDateColor: 'rgba(0, 0, 0, 0.76)',
tooltipDateInterval: true, tooltipDateInterval: true,
tooltipBackgroundColor: 'rgba(255, 255, 255, 0.76)', tooltipBackgroundColor: 'rgba(255, 255, 255, 0.76)',
tooltipBackgroundBlur: 4 tooltipBackgroundBlur: 4,
comparisonEnabled: false,
timeForComparison: 'previousInterval',
comparisonCustomIntervalValue: 7200000,
comparisonXAxis: mergeDeep({} as TimeSeriesChartXAxisSettings,
defaultTimeSeriesChartXAxisSettings,
{ position: AxisPosition.top } as TimeSeriesChartXAxisSettings)
}; };
export interface SeriesFillSettings { export interface SeriesFillSettings {
@ -798,7 +810,7 @@ export interface BarSeriesSettings {
backgroundSettings: SeriesFillSettings; backgroundSettings: SeriesFillSettings;
} }
export interface TimeSeriesChartKeySettings { export interface TimeSeriesChartKeySettings extends DataKeySettingsWithComparison {
yAxisId: TimeSeriesChartYAxisId; yAxisId: TimeSeriesChartYAxisId;
showInLegend: boolean; showInLegend: boolean;
dataHiddenByDefault: boolean; dataHiddenByDefault: boolean;
@ -870,10 +882,16 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
end: 0 end: 0
} }
} }
},
comparisonSettings: {
showValuesForComparison: false,
comparisonValuesLabel: '',
color: ''
} }
}; };
export interface TimeSeriesChartDataItem extends EChartsSeriesItem { export interface TimeSeriesChartDataItem extends EChartsSeriesItem {
xAxisIndex: number;
yAxisId: TimeSeriesChartYAxisId; yAxisId: TimeSeriesChartYAxisId;
yAxisIndex: number; yAxisIndex: number;
option?: LineSeriesOption | CustomSeriesOption; option?: LineSeriesOption | CustomSeriesOption;
@ -894,13 +912,23 @@ export interface TimeSeriesChartThresholdItem {
option?: LineSeriesOption; option?: LineSeriesOption;
} }
export interface TimeSeriesChartYAxis { export interface TimeSeriesChartAxis {
id: string; id: string;
settings: TimeSeriesChartAxisSettings;
option: CartesianAxisOption;
}
export interface TimeSeriesChartYAxis extends TimeSeriesChartAxis {
decimals: number; decimals: number;
settings: TimeSeriesChartYAxisSettings; settings: TimeSeriesChartYAxisSettings;
option: YAXisOption & ValueAxisBaseOption; option: YAXisOption & ValueAxisBaseOption;
} }
export interface TimeSeriesChartXAxis extends TimeSeriesChartAxis {
settings: TimeSeriesChartXAxisSettings;
option: XAXisOption;
}
export const createTimeSeriesYAxis = (units: string, export const createTimeSeriesYAxis = (units: string,
decimals: number, decimals: number,
settings: TimeSeriesChartYAxisSettings, settings: TimeSeriesChartYAxisSettings,
@ -944,6 +972,7 @@ export const createTimeSeriesYAxis = (units: string,
decimals, decimals,
settings, settings,
option: { option: {
mainType: 'yAxis',
show: settings.show, show: settings.show,
type: 'value', type: 'value',
position: settings.position, position: settings.position,
@ -1011,75 +1040,87 @@ export const createTimeSeriesYAxis = (units: string,
}; };
}; };
export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettings, export const createTimeSeriesXAxis = (id: string,
min: number, max: number, settings: TimeSeriesChartXAxisSettings,
datePipe: DatePipe, min: number, max: number,
darkMode: boolean): XAXisOption => { datePipe: DatePipe,
darkMode: boolean): TimeSeriesChartXAxis => {
const xAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont, const xAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont,
settings.tickLabelColor, darkMode, 'axis.tickLabel'); settings.tickLabelColor, darkMode, 'axis.tickLabel');
const xAxisNameStyle = createChartTextStyle(settings.labelFont, const xAxisNameStyle = createChartTextStyle(settings.labelFont,
settings.labelColor, darkMode, 'axis.label'); settings.labelColor, darkMode, 'axis.label');
const ticksFormat = mergeDeep({}, defaultXAxisTicksFormat, settings.ticksFormat); const ticksFormat = mergeDeep({}, defaultXAxisTicksFormat, settings.ticksFormat);
return { return {
show: settings.show, id,
type: 'time', settings,
scale: true, option: {
position: settings.position, mainType: 'xAxis',
name: settings.label, show: settings.show,
nameLocation: 'middle', type: 'time',
nameTextStyle: { scale: true,
color: xAxisNameStyle.color, position: settings.position,
fontStyle: xAxisNameStyle.fontStyle, id,
fontWeight: xAxisNameStyle.fontWeight, name: settings.label,
fontFamily: xAxisNameStyle.fontFamily, nameLocation: 'middle',
fontSize: xAxisNameStyle.fontSize nameTextStyle: {
}, color: xAxisNameStyle.color,
axisTick: { fontStyle: xAxisNameStyle.fontStyle,
show: settings.showTicks, fontWeight: xAxisNameStyle.fontWeight,
lineStyle: { fontFamily: xAxisNameStyle.fontFamily,
color: prepareChartThemeColor(settings.ticksColor, darkMode, 'axis.ticks') fontSize: xAxisNameStyle.fontSize
} },
}, axisPointer: {
axisLabel: { shadowStyle: {
show: settings.showTickLabels, color: id === 'main' ? 'rgba(210,219,238,0.2)' : 'rgba(150,150,150,0.1)'
color: xAxisTickLabelStyle.color,
fontStyle: xAxisTickLabelStyle.fontStyle,
fontWeight: xAxisTickLabelStyle.fontWeight,
fontFamily: xAxisTickLabelStyle.fontFamily,
fontSize: xAxisTickLabelStyle.fontSize,
hideOverlap: true,
/** Min/Max time label always visible **/
/* alignMinLabel: 'left',
alignMaxLabel: 'right',
showMinLabel: true,
showMaxLabel: true, */
formatter: (value: number, _index: number, extra: {level: number}) => {
const unit = tsToFormatTimeUnit(value);
const format = ticksFormat[unit];
const formatted = datePipe.transform(value, format);
if (extra.level > 0) {
return `{primary|${formatted}}`;
} else {
return formatted;
} }
} },
}, axisTick: {
axisLine: { show: settings.showTicks,
show: settings.showLine, lineStyle: {
onZero: false, color: prepareChartThemeColor(settings.ticksColor, darkMode, 'axis.ticks')
lineStyle: { }
color: prepareChartThemeColor(settings.lineColor, darkMode, 'axis.line') },
} axisLabel: {
}, show: settings.showTickLabels,
splitLine: { color: xAxisTickLabelStyle.color,
show: settings.showSplitLines, fontStyle: xAxisTickLabelStyle.fontStyle,
lineStyle: { fontWeight: xAxisTickLabelStyle.fontWeight,
color: prepareChartThemeColor(settings.splitLinesColor, darkMode, 'axis.splitLine') fontFamily: xAxisTickLabelStyle.fontFamily,
} fontSize: xAxisTickLabelStyle.fontSize,
}, hideOverlap: true,
min, /** Min/Max time label always visible **/
max, /* alignMinLabel: 'left',
bandWidthCalculator: timeAxisBandWidthCalculator alignMaxLabel: 'right',
showMinLabel: true,
showMaxLabel: true, */
formatter: (value: number, _index: number, extra: {level: number}) => {
const unit = tsToFormatTimeUnit(value);
const format = ticksFormat[unit];
const formatted = datePipe.transform(value, format);
if (extra.level > 0) {
return `{primary|${formatted}}`;
} else {
return formatted;
}
}
},
axisLine: {
show: settings.showLine,
onZero: false,
lineStyle: {
color: prepareChartThemeColor(settings.lineColor, darkMode, 'axis.line')
}
},
splitLine: {
show: settings.showSplitLines,
lineStyle: {
color: prepareChartThemeColor(settings.splitLinesColor, darkMode, 'axis.splitLine')
}
},
min,
max,
bandWidthCalculator: timeAxisBandWidthCalculator
}
}; };
}; };
@ -1098,6 +1139,13 @@ export const createTimeSeriesVisualMapOption = (settings: TimeSeriesChartVisualM
} : undefined } : undefined
}); });
export const updateXAxisTimeWindow = (option: XAXisOption,
timeWindow: WidgetTimewindow) => {
option.min = timeWindow.minTime;
option.max = timeWindow.maxTime;
(option as any).tbTimeWindow = timeWindow;
};
export const generateChartData = (dataItems: TimeSeriesChartDataItem[], export const generateChartData = (dataItems: TimeSeriesChartDataItem[],
thresholdItems: TimeSeriesChartThresholdItem[], thresholdItems: TimeSeriesChartThresholdItem[],
stack: boolean, stack: boolean,
@ -1262,6 +1310,7 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[],
}; };
export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings, export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings,
xAxisList: TimeSeriesChartXAxis[],
yAxisList: TimeSeriesChartYAxis[], yAxisList: TimeSeriesChartYAxis[],
dataItems: TimeSeriesChartDataItem[], dataItems: TimeSeriesChartDataItem[],
darkMode: boolean): EChartsOption => { darkMode: boolean): EChartsOption => {
@ -1278,12 +1327,14 @@ export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChart
} }
} }
if (Array.isArray(options.xAxis)) { if (Array.isArray(options.xAxis)) {
for (const xAxis of options.xAxis) { for (let i = 0; i < options.xAxis.length; i++) {
xAxis.nameTextStyle.color = prepareChartThemeColor(settings.xAxis.labelColor, darkMode, 'axis.label'); const xAxis = options.xAxis[i];
xAxis.axisLabel.color = prepareChartThemeColor(settings.xAxis.tickLabelColor, darkMode, 'axis.tickLabel'); const xAxisSettings = xAxisList[i].settings;
xAxis.axisLine.lineStyle.color = prepareChartThemeColor(settings.xAxis.lineColor, darkMode, 'axis.line'); xAxis.nameTextStyle.color = prepareChartThemeColor(xAxisSettings.labelColor, darkMode, 'axis.label');
xAxis.axisTick.lineStyle.color = prepareChartThemeColor(settings.xAxis.ticksColor, darkMode, 'axis.ticks'); xAxis.axisLabel.color = prepareChartThemeColor(xAxisSettings.tickLabelColor, darkMode, 'axis.tickLabel');
xAxis.splitLine.lineStyle.color = prepareChartThemeColor(settings.xAxis.splitLinesColor, darkMode, 'axis.splitLine'); xAxis.axisLine.lineStyle.color = prepareChartThemeColor(xAxisSettings.lineColor, darkMode, 'axis.line');
xAxis.axisTick.lineStyle.color = prepareChartThemeColor(xAxisSettings.ticksColor, darkMode, 'axis.ticks');
xAxis.splitLine.lineStyle.color = prepareChartThemeColor(xAxisSettings.splitLinesColor, darkMode, 'axis.splitLine');
} }
} }
for (const item of dataItems) { for (const item of dataItems) {
@ -1322,6 +1373,7 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
seriesOption = { seriesOption = {
id: item.id, id: item.id,
dataGroupId: item.id, dataGroupId: item.id,
xAxisIndex: item.xAxisIndex,
yAxisIndex: item.yAxisIndex, yAxisIndex: item.yAxisIndex,
name: item.dataKey.label, name: item.dataKey.label,
color: seriesColor, color: seriesColor,

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

@ -16,16 +16,16 @@
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { import {
AxisPosition,
calculateThresholdsOffset, calculateThresholdsOffset,
createTimeSeriesVisualMapOption, createTimeSeriesVisualMapOption,
createTimeSeriesXAxisOption, createTimeSeriesXAxis,
createTimeSeriesYAxis, createTimeSeriesYAxis,
defaultTimeSeriesChartYAxisSettings, defaultTimeSeriesChartYAxisSettings,
generateChartData, generateChartData,
LineSeriesStepType, LineSeriesStepType,
parseThresholdData, parseThresholdData,
SeriesLabelPosition, SeriesLabelPosition,
TimeSeriesChartAxis,
TimeSeriesChartDataItem, TimeSeriesChartDataItem,
timeSeriesChartDefaultSettings, timeSeriesChartDefaultSettings,
timeSeriesChartKeyDefaultSettings, timeSeriesChartKeyDefaultSettings,
@ -38,16 +38,17 @@ import {
TimeSeriesChartThresholdItem, TimeSeriesChartThresholdItem,
TimeSeriesChartThresholdType, TimeSeriesChartThresholdType,
TimeSeriesChartType, TimeSeriesChartType,
TimeSeriesChartXAxis,
TimeSeriesChartYAxis, TimeSeriesChartYAxis,
TimeSeriesChartYAxisId, TimeSeriesChartYAxisId,
TimeSeriesChartYAxisSettings, TimeSeriesChartYAxisSettings,
updateDarkMode updateDarkMode,
updateXAxisTimeWindow
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { import {
adjustTimeAxisExtentToData, adjustTimeAxisExtentToData,
calculateXAxisHeight, calculateAxisSize,
calculateYAxisWidth,
createTooltipValueFormatFunction, createTooltipValueFormatFunction,
ECharts, ECharts,
echartsModule, echartsModule,
@ -58,8 +59,7 @@ import {
EChartsTooltipValueFormatFunction, EChartsTooltipValueFormatFunction,
getAxisExtent, getAxisExtent,
getFocusedSeriesIndex, getFocusedSeriesIndex,
measureXAxisNameHeight, measureAxisNameSize,
measureYAxisNameWidth,
toNamedData toNamedData
} from '@home/components/widget/lib/chart/echarts-widget.models'; } from '@home/components/widget/lib/chart/echarts-widget.models';
import { DateFormatProcessor } from '@shared/models/widget-settings.models'; import { DateFormatProcessor } from '@shared/models/widget-settings.models';
@ -120,6 +120,10 @@ export class TbTimeSeriesChart {
private readonly settings: TimeSeriesChartSettings; private readonly settings: TimeSeriesChartSettings;
private readonly comparisonEnabled: boolean;
private readonly stackMode: boolean;
private xAxisList: TimeSeriesChartXAxis[] = [];
private yAxisList: TimeSeriesChartYAxis[] = []; private yAxisList: TimeSeriesChartYAxis[] = [];
private dataItems: TimeSeriesChartDataItem[] = []; private dataItems: TimeSeriesChartDataItem[] = [];
private thresholdItems: TimeSeriesChartThresholdItem[] = []; private thresholdItems: TimeSeriesChartThresholdItem[] = [];
@ -163,6 +167,8 @@ export class TbTimeSeriesChart {
this.settings = mergeDeep({} as TimeSeriesChartSettings, this.settings = mergeDeep({} as TimeSeriesChartSettings,
timeSeriesChartDefaultSettings, timeSeriesChartDefaultSettings,
this.inputSettings as TimeSeriesChartSettings); this.inputSettings as TimeSeriesChartSettings);
this.comparisonEnabled = !!this.ctx.defaultSubscription.comparisonEnabled;
this.stackMode = !this.comparisonEnabled && this.settings.stack;
if (this.settings.states && this.settings.states.length) { if (this.settings.states && this.settings.states.length) {
this.stateValueConverter = new TimeSeriesChartStateValueConverter(this.ctx.dashboard.utils, this.settings.states); this.stateValueConverter = new TimeSeriesChartStateValueConverter(this.ctx.dashboard.utils, this.settings.states);
this.tooltipValueFormatFunction = this.stateValueConverter.tooltipFormatter; this.tooltipValueFormatFunction = this.stateValueConverter.tooltipFormatter;
@ -170,6 +176,7 @@ export class TbTimeSeriesChart {
const $dashboardPageElement = this.ctx.$containerParent.parents('.tb-dashboard-page'); const $dashboardPageElement = this.ctx.$containerParent.parents('.tb-dashboard-page');
const dashboardPageElement = $dashboardPageElement.length ? $($dashboardPageElement[$dashboardPageElement.length-1]) : null; const dashboardPageElement = $dashboardPageElement.length ? $($dashboardPageElement[$dashboardPageElement.length-1]) : null;
this.darkMode = this.settings.darkMode || dashboardPageElement?.hasClass('dark'); this.darkMode = this.settings.darkMode || dashboardPageElement?.hasClass('dark');
this.setupXAxes();
this.setupYAxes(); this.setupYAxes();
this.setupData(); this.setupData();
this.setupThresholds(); this.setupThresholds();
@ -216,14 +223,16 @@ export class TbTimeSeriesChart {
} }
this.onResize(); this.onResize();
if (this.timeSeriesChart) { if (this.timeSeriesChart) {
this.timeSeriesChartOptions.xAxis[0].min = this.ctx.defaultSubscription.timeWindow.minTime; updateXAxisTimeWindow(this.xAxisList[0].option, this.ctx.defaultSubscription.timeWindow);
this.timeSeriesChartOptions.xAxis[0].max = this.ctx.defaultSubscription.timeWindow.maxTime;
this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow;
if (this.noAggregation) { if (this.noAggregation) {
this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'line'; this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'line';
} else { } else {
this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'shadow'; this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'shadow';
} }
if (this.comparisonEnabled) {
updateXAxisTimeWindow(this.xAxisList[1].option, this.ctx.defaultSubscription.comparisonTimeWindow);
}
this.timeSeriesChartOptions.xAxis = this.xAxisList.map(axis => axis.option);
if (this.hasVisualMap) { if (this.hasVisualMap) {
(this.timeSeriesChartOptions.visualMap as PiecewiseVisualMapOption).selected = this.visualMapSelectedRanges; (this.timeSeriesChartOptions.visualMap as PiecewiseVisualMapOption).selected = this.visualMapSelectedRanges;
} }
@ -300,7 +309,7 @@ export class TbTimeSeriesChart {
this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option);
mergeList.push('yAxis'); mergeList.push('yAxis');
} }
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, this.settings.stack ? {notMerge: true} : {replaceMerge: mergeList}); this.timeSeriesChart.setOption(this.timeSeriesChartOptions, this.stackMode ? {notMerge: true} : {replaceMerge: mergeList});
this.updateAxes(); this.updateAxes();
dataKey.hidden = !enable; dataKey.hidden = !enable;
if (enable) { if (enable) {
@ -343,7 +352,7 @@ export class TbTimeSeriesChart {
this.darkMode = darkMode; this.darkMode = darkMode;
if (this.timeSeriesChart) { if (this.timeSeriesChart) {
this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions, this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions,
this.settings, this.yAxisList, this.dataItems, darkMode); this.settings, this.xAxisList, this.yAxisList, this.dataItems, darkMode);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions); this.timeSeriesChart.setOption(this.timeSeriesChartOptions);
} }
} }
@ -392,12 +401,16 @@ export class TbTimeSeriesChart {
if (!Object.keys(this.settings.yAxes).includes(yAxisId)) { if (!Object.keys(this.settings.yAxes).includes(yAxisId)) {
yAxisId = 'default'; yAxisId = 'default';
} }
const comparisonItem = this.comparisonEnabled && dataKey.isAdditional;
const xAxisIndex = comparisonItem ? 1 : 0;
this.dataItems.push({ this.dataItems.push({
id: this.nextComponentId(), id: this.nextComponentId(),
units, units,
decimals, decimals,
xAxisIndex,
yAxisId, yAxisId,
yAxisIndex: this.getYAxisIndex(yAxisId), yAxisIndex: this.getYAxisIndex(yAxisId),
comparisonItem,
datasource, datasource,
dataKey, dataKey,
data: namedData, data: namedData,
@ -488,6 +501,18 @@ export class TbTimeSeriesChart {
this.subscribeForEntityThresholds(thresholdDatasources); this.subscribeForEntityThresholds(thresholdDatasources);
} }
private setupXAxes(): void {
const mainXAxis = createTimeSeriesXAxis('main', this.settings.xAxis, this.ctx.defaultSubscription.timeWindow.minTime,
this.ctx.defaultSubscription.timeWindow.maxTime, this.ctx.date, this.darkMode);
this.xAxisList.push(mainXAxis);
if (this.comparisonEnabled) {
const comparisonXAxis = createTimeSeriesXAxis('comparison', this.settings.comparisonXAxis,
this.ctx.defaultSubscription.comparisonTimeWindow.minTime, this.ctx.defaultSubscription.comparisonTimeWindow.maxTime,
this.ctx.date, this.darkMode);
this.xAxisList.push(comparisonXAxis);
}
}
private setupYAxes(): void { private setupYAxes(): void {
const yAxisSettingsList = Object.values(this.settings.yAxes); const yAxisSettingsList = Object.values(this.settings.yAxes);
yAxisSettingsList.sort((a1, a2) => a1.order - a2.order); yAxisSettingsList.sort((a1, a2) => a1.order - a2.order);
@ -592,10 +617,7 @@ export class TbTimeSeriesChart {
right: this.settings.dataZoom ? 5 : 0, right: this.settings.dataZoom ? 5 : 0,
bottom: this.minBottomOffset() bottom: this.minBottomOffset()
}], }],
xAxis: [ xAxis: this.xAxisList.map(axis => axis.option),
createTimeSeriesXAxisOption(this.settings.xAxis, this.ctx.defaultSubscription.timeWindow.minTime,
this.ctx.defaultSubscription.timeWindow.maxTime, this.ctx.date, this.darkMode)
],
yAxis: this.yAxisList.map(axis => axis.option), yAxis: this.yAxisList.map(axis => axis.option),
dataZoom: [ dataZoom: [
{ {
@ -655,10 +677,10 @@ export class TbTimeSeriesChart {
private updateSeries(): void { private updateSeries(): void {
this.timeSeriesChartOptions.series = generateChartData(this.dataItems, this.thresholdItems, this.timeSeriesChartOptions.series = generateChartData(this.dataItems, this.thresholdItems,
this.settings.stack, this.stackMode,
this.noAggregation, this.noAggregation,
this.barRenderSharedContext, this.darkMode); this.barRenderSharedContext, this.darkMode);
if (this.stateData) { if (this.stateData && !this.comparisonEnabled) {
adjustTimeAxisExtentToData(this.timeSeriesChartOptions.xAxis[0], this.dataItems, adjustTimeAxisExtentToData(this.timeSeriesChartOptions.xAxis[0], this.dataItems,
this.ctx.defaultSubscription.timeWindow.minTime, this.ctx.defaultSubscription.timeWindow.minTime,
this.ctx.defaultSubscription.timeWindow.maxTime); this.ctx.defaultSubscription.timeWindow.maxTime);
@ -667,37 +689,26 @@ export class TbTimeSeriesChart {
private updateAxes(lazy = true) { private updateAxes(lazy = true) {
const leftAxisList = this.yAxisList.filter(axis => axis.option.position === 'left'); const leftAxisList = this.yAxisList.filter(axis => axis.option.position === 'left');
let res = this.updateYAxisOffset(leftAxisList); let res = this.updateAxisOffset(leftAxisList);
let leftOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0); let leftOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0);
let changed = res.changed; let changed = res.changed;
const rightAxisList = this.yAxisList.filter(axis => axis.option.position === 'right'); const rightAxisList = this.yAxisList.filter(axis => axis.option.position === 'right');
res = this.updateYAxisOffset(rightAxisList); res = this.updateAxisOffset(rightAxisList);
let rightOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0); let rightOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0);
changed = changed || res.changed; changed = changed || res.changed;
let bottomOffset = this.minBottomOffset(); let bottomOffset = this.minBottomOffset();
const minTopOffset = this.minTopOffset(); const minTopOffset = this.minTopOffset();
let topOffset = minTopOffset;
if (this.timeSeriesChartOptions.xAxis[0].show) { const topAxisList = this.xAxisList.filter(axis => axis.option.position === 'top');
const xAxisHeight = calculateXAxisHeight(this.timeSeriesChart); res = this.updateAxisOffset(topAxisList);
if (this.timeSeriesChartOptions.xAxis[0].position === AxisPosition.bottom) { const topOffset = Math.max(res.offset, minTopOffset);
bottomOffset += xAxisHeight; changed = changed || res.changed;
} else {
topOffset = Math.max(minTopOffset, xAxisHeight); const bottomAxisList = this.xAxisList.filter(axis => axis.option.position === 'bottom');
} res = this.updateAxisOffset(bottomAxisList);
if (this.settings.xAxis.label) { bottomOffset += res.offset;
const nameHeight = measureXAxisNameHeight(this.timeSeriesChart, this.timeSeriesChartOptions.xAxis[0].name); changed = changed || res.changed;
if (this.timeSeriesChartOptions.xAxis[0].position === AxisPosition.bottom) {
bottomOffset += nameHeight;
} else {
topOffset = Math.max(minTopOffset, xAxisHeight + nameHeight);
}
const nameGap = xAxisHeight;
if (this.timeSeriesChartOptions.xAxis[0].nameGap !== nameGap) {
this.timeSeriesChartOptions.xAxis[0].nameGap = nameGap;
changed = true;
}
}
}
const thresholdsOffset = calculateThresholdsOffset(this.timeSeriesChart, this.thresholdItems, this.yAxisList); const thresholdsOffset = calculateThresholdsOffset(this.timeSeriesChart, this.thresholdItems, this.yAxisList);
leftOffset = Math.max(leftOffset, thresholdsOffset[0]); leftOffset = Math.max(leftOffset, thresholdsOffset[0]);
@ -715,6 +726,7 @@ export class TbTimeSeriesChart {
} }
if (changed) { if (changed) {
this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option);
this.timeSeriesChartOptions.xAxis = this.xAxisList.map(axis => axis.option);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis', 'xAxis', 'grid'], lazyUpdate: lazy}); this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis', 'xAxis', 'grid'], lazyUpdate: lazy});
} }
if (this.yAxisList.length) { if (this.yAxisList.length) {
@ -730,45 +742,45 @@ export class TbTimeSeriesChart {
} }
} }
private updateYAxisOffset(axisList: TimeSeriesChartYAxis[]): {offset: number; changed: boolean} { private updateAxisOffset(axisList: TimeSeriesChartAxis[]): {offset: number; changed: boolean} {
const result = {offset: 0, changed: false}; const result = {offset: 0, changed: false};
let width = 0; let size = 0;
for (const yAxis of axisList) { for (const axis of axisList) {
const newWidth = calculateYAxisWidth(this.timeSeriesChart, yAxis.id); const newSize = calculateAxisSize(this.timeSeriesChart, axis.option.mainType, axis.id);
if (width && newWidth) { if (size && newSize) {
result.offset += 5; result.offset += 5;
} }
width = newWidth; size = newSize;
const showLine = !!width && yAxis.settings.showLine; const showLine = !!size && axis.settings.showLine;
if (yAxis.option.axisLine.show !== showLine) { if (axis.option.axisLine.show !== showLine) {
yAxis.option.axisLine.show = showLine; axis.option.axisLine.show = showLine;
result.changed = true; result.changed = true;
} }
if (yAxis.option.offset !== result.offset) { if (axis.option.offset !== result.offset) {
yAxis.option.offset = result.offset; axis.option.offset = result.offset;
result.changed = true; result.changed = true;
} }
if (yAxis.settings.label) { if (axis.settings.label) {
if (!width) { if (!size) {
if (yAxis.option.name) { if (axis.option.name) {
yAxis.option.name = null; axis.option.name = null;
result.changed = true; result.changed = true;
} }
} else { } else {
if (!yAxis.option.name) { if (!axis.option.name) {
yAxis.option.name = yAxis.settings.label; axis.option.name = axis.settings.label;
result.changed = true; result.changed = true;
} }
const nameGap = width; const nameGap = size;
if (yAxis.option.nameGap !== nameGap) { if (axis.option.nameGap !== nameGap) {
yAxis.option.nameGap = nameGap; axis.option.nameGap = nameGap;
result.changed = true; result.changed = true;
} }
const nameWidth = measureYAxisNameWidth(this.timeSeriesChart, yAxis.id, yAxis.settings.label); const nameSize = measureAxisNameSize(this.timeSeriesChart, axis.option.mainType, axis.id, axis.settings.label);
result.offset += nameWidth; result.offset += nameSize;
} }
} }
result.offset += width; result.offset += size;
} }
return result; return result;
} }

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

@ -18,11 +18,10 @@
/// <reference path="../../../../../../../src/typings/jquery.flot.typings.d.ts" /> /// <reference path="../../../../../../../src/typings/jquery.flot.typings.d.ts" />
import { import {
DataKey, DataKey, DataKeySettingsWithComparison,
Datasource, Datasource,
DatasourceData, DatasourceData,
FormattedData, FormattedData,
JsonSettingsSchema,
LegendConfig LegendConfig
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
@ -219,13 +218,7 @@ export interface TbFlotKeyThreshold {
color: string; color: string;
} }
export interface TbFlotKeyComparisonSettings { export interface TbFlotKeySettings extends DataKeySettingsWithComparison {
showValuesForComparison: boolean;
comparisonValuesLabel: string;
color: string;
}
export interface TbFlotKeySettings {
excludeFromStacking: boolean; excludeFromStacking: boolean;
hideDataByDefault: boolean; hideDataByDefault: boolean;
disableDataHiding: boolean; disableDataHiding: boolean;
@ -249,7 +242,6 @@ export interface TbFlotKeySettings {
axisPosition: TbFlotYAxisPosition; axisPosition: TbFlotYAxisPosition;
axisTicksFormatter: string; axisTicksFormatter: string;
thresholds: TbFlotKeyThreshold[]; thresholds: TbFlotKeyThreshold[];
comparisonSettings: TbFlotKeyComparisonSettings;
} }
export interface TbFlotLatestKeySettings { export interface TbFlotLatestKeySettings {

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

@ -94,6 +94,11 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
lineSettings: [seriesSettings.lineSettings, []], lineSettings: [seriesSettings.lineSettings, []],
barSettings: [seriesSettings.barSettings, []], barSettings: [seriesSettings.barSettings, []],
tooltipValueFormatter: [seriesSettings.tooltipValueFormatter, []], tooltipValueFormatter: [seriesSettings.tooltipValueFormatter, []],
comparisonSettings: this.fb.group({
showValuesForComparison: [seriesSettings.comparisonSettings?.showValuesForComparison, []],
comparisonValuesLabel: [seriesSettings.comparisonSettings?.comparisonValuesLabel, []],
color: [seriesSettings.comparisonSettings?.color, []]
})
}); });
} }

16
ui-ngx/src/app/shared/models/widget.models.ts

@ -21,7 +21,6 @@ import { AggregationType, ComparisonDuration, Timewindow } from '@shared/models/
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { DataKeyType } from './telemetry/telemetry.models'; import { DataKeyType } from './telemetry/telemetry.models';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import * as moment_ from 'moment';
import { import {
AlarmFilter, AlarmFilter,
AlarmFilterConfig, AlarmFilterConfig,
@ -710,10 +709,23 @@ export const defaultWidgetAction = (setEntityId = true): WidgetAction => ({
export interface WidgetComparisonSettings { export interface WidgetComparisonSettings {
comparisonEnabled?: boolean; comparisonEnabled?: boolean;
timeForComparison?: moment_.unitOfTime.DurationConstructor; timeForComparison?: ComparisonDuration;
comparisonCustomIntervalValue?: number; comparisonCustomIntervalValue?: number;
} }
export interface DataKeyComparisonSettings {
showValuesForComparison: boolean;
comparisonValuesLabel: string;
color: string;
}
export interface DataKeySettingsWithComparison {
comparisonSettings?: DataKeyComparisonSettings;
}
export const isDataKeySettingsWithComparison = (settings: any): settings is DataKeySettingsWithComparison =>
'comparisonSettings' in settings;
export interface WidgetSettings { export interface WidgetSettings {
[key: string]: any; [key: string]: any;
} }

Loading…
Cancel
Save