UI: Refactor range chart widget to use time series chart class.
This commit is contained in:
parent
7900d5f29c
commit
2fb604e398
@ -32,48 +32,38 @@ import {
|
|||||||
backgroundStyle,
|
backgroundStyle,
|
||||||
ColorRange,
|
ColorRange,
|
||||||
ComponentStyle,
|
ComponentStyle,
|
||||||
DateFormatProcessor,
|
|
||||||
filterIncludingColorRanges,
|
filterIncludingColorRanges,
|
||||||
getDataKey,
|
getDataKey,
|
||||||
overlayStyle,
|
overlayStyle,
|
||||||
sortedColorRange,
|
sortedColorRange,
|
||||||
textStyle
|
textStyle
|
||||||
} from '@shared/models/widget-settings.models';
|
} from '@shared/models/widget-settings.models';
|
||||||
import { ResizeObserver } from '@juggle/resize-observer';
|
import { isDefinedAndNotNull, isNumber } from '@core/utils';
|
||||||
import * as echarts from 'echarts/core';
|
|
||||||
import { formatValue, isDefinedAndNotNull, isNumber } from '@core/utils';
|
|
||||||
import { rangeChartDefaultSettings, RangeChartWidgetSettings } from './range-chart-widget.models';
|
import { rangeChartDefaultSettings, RangeChartWidgetSettings } from './range-chart-widget.models';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { ImagePipe } from '@shared/pipe/image.pipe';
|
import { ImagePipe } from '@shared/pipe/image.pipe';
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
import { DeepPartial } from '@shared/models/common';
|
||||||
import {
|
import {
|
||||||
ECharts,
|
createTimeSeriesChartVisualMapPiece,
|
||||||
echartsModule,
|
SeriesFillType,
|
||||||
EChartsOption,
|
TimeSeriesChartKeySettings,
|
||||||
echartsTooltipFormatter, timeAxisBandWidthCalculator,
|
TimeSeriesChartSettings,
|
||||||
toNamedData
|
TimeSeriesChartShape,
|
||||||
} from '@home/components/widget/lib/chart/echarts-widget.models';
|
TimeSeriesChartThreshold,
|
||||||
import { CallbackDataParams } from 'echarts/types/dist/shared';
|
TimeSeriesChartThresholdType, TimeSeriesChartVisualMapPiece
|
||||||
import { AggregationType } from '@shared/models/time/time.models';
|
} from '@home/components/widget/lib/chart/time-series-chart.models';
|
||||||
|
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
|
||||||
interface VisualPiece {
|
|
||||||
lt?: number;
|
|
||||||
gt?: number;
|
|
||||||
lte?: number;
|
|
||||||
gte?: number;
|
|
||||||
value?: number;
|
|
||||||
color?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RangeItem {
|
interface RangeItem {
|
||||||
index: number;
|
index: number;
|
||||||
from?: number;
|
from?: number;
|
||||||
to?: number;
|
to?: number;
|
||||||
piece: VisualPiece;
|
|
||||||
color: string;
|
color: string;
|
||||||
label: string;
|
label: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
piece: TimeSeriesChartVisualMapPiece;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangeItemLabel = (from?: number, to?: number): string => {
|
const rangeItemLabel = (from?: number, to?: number): string => {
|
||||||
@ -92,25 +82,6 @@ const rangeItemLabel = (from?: number, to?: number): string => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toVisualPiece = (color: string, from?: number, to?: number): VisualPiece => {
|
|
||||||
const piece: VisualPiece = {
|
|
||||||
color
|
|
||||||
};
|
|
||||||
if (isNumber(from) && isNumber(to)) {
|
|
||||||
if (from === to) {
|
|
||||||
piece.value = from;
|
|
||||||
} else {
|
|
||||||
piece.gte = from;
|
|
||||||
piece.lt = to;
|
|
||||||
}
|
|
||||||
} else if (isNumber(from)) {
|
|
||||||
piece.gte = from;
|
|
||||||
} else if (isNumber(to)) {
|
|
||||||
piece.lt = to;
|
|
||||||
}
|
|
||||||
return piece;
|
|
||||||
};
|
|
||||||
|
|
||||||
const toRangeItems = (colorRanges: Array<ColorRange>): RangeItem[] => {
|
const toRangeItems = (colorRanges: Array<ColorRange>): RangeItem[] => {
|
||||||
const rangeItems: RangeItem[] = [];
|
const rangeItems: RangeItem[] = [];
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
@ -134,7 +105,7 @@ const toRangeItems = (colorRanges: Array<ColorRange>): RangeItem[] => {
|
|||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
label: rangeItemLabel(from, to),
|
label: rangeItemLabel(from, to),
|
||||||
piece: toVisualPiece(range.color, from, to)
|
piece: createTimeSeriesChartVisualMapPiece(range.color, from, to)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (!isNumber(from) || !isNumber(to)) {
|
if (!isNumber(from) || !isNumber(to)) {
|
||||||
@ -198,23 +169,12 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
|
|||||||
disabledLegendLabelStyle: ComponentStyle;
|
disabledLegendLabelStyle: ComponentStyle;
|
||||||
visibleRangeItems: RangeItem[];
|
visibleRangeItems: RangeItem[];
|
||||||
|
|
||||||
private get noAggregation(): boolean {
|
|
||||||
return this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private rangeItems: RangeItem[];
|
|
||||||
|
|
||||||
private shapeResize$: ResizeObserver;
|
|
||||||
|
|
||||||
private decimals = 0;
|
private decimals = 0;
|
||||||
private units = '';
|
private units = '';
|
||||||
|
|
||||||
private drawChartPending = false;
|
private rangeItems: RangeItem[];
|
||||||
private rangeChart: ECharts;
|
|
||||||
private rangeChartOptions: EChartsOption;
|
|
||||||
private selectedRanges: {[key: number]: boolean} = {};
|
|
||||||
|
|
||||||
private tooltipDateFormat: DateFormatProcessor;
|
private timeSeriesChart: TbTimeSeriesChart;
|
||||||
|
|
||||||
constructor(private imagePipe: ImagePipe,
|
constructor(private imagePipe: ImagePipe,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
@ -235,16 +195,25 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
|
|||||||
if (dataKey?.units) {
|
if (dataKey?.units) {
|
||||||
this.units = dataKey.units;
|
this.units = dataKey.units;
|
||||||
}
|
}
|
||||||
|
if (dataKey) {
|
||||||
|
dataKey.settings = {
|
||||||
|
type: 'line',
|
||||||
|
lineSettings: {
|
||||||
|
showLine: true,
|
||||||
|
smooth: false,
|
||||||
|
showPoints: false,
|
||||||
|
fillAreaSettings: {
|
||||||
|
type: this.settings.fillArea ? 'default' : SeriesFillType.none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as DeepPartial<TimeSeriesChartKeySettings>;
|
||||||
|
}
|
||||||
|
|
||||||
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
|
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
|
||||||
this.overlayStyle = overlayStyle(this.settings.background.overlay);
|
this.overlayStyle = overlayStyle(this.settings.background.overlay);
|
||||||
|
|
||||||
this.rangeItems = toRangeItems(this.settings.rangeColors);
|
this.rangeItems = toRangeItems(this.settings.rangeColors);
|
||||||
this.visibleRangeItems = this.rangeItems.filter(item => item.visible);
|
this.visibleRangeItems = this.rangeItems.filter(item => item.visible);
|
||||||
for (const range of this.rangeItems) {
|
|
||||||
this.selectedRanges[range.index] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showLegend = this.settings.showLegend && !!this.rangeItems.length;
|
this.showLegend = this.settings.showLegend && !!this.rangeItems.length;
|
||||||
|
|
||||||
@ -254,193 +223,90 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
|
|||||||
this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont);
|
this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont);
|
||||||
this.legendLabelStyle.color = this.settings.legendLabelColor;
|
this.legendLabelStyle.color = this.settings.legendLabelColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.settings.showTooltip && this.settings.tooltipShowDate) {
|
|
||||||
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
if (this.drawChartPending) {
|
const thresholds: DeepPartial<TimeSeriesChartThreshold>[] = getMarkPoints(this.rangeItems).map(item => ({
|
||||||
this.drawChart();
|
type: TimeSeriesChartThresholdType.constant,
|
||||||
|
yAxisId: 'default',
|
||||||
|
units: this.units,
|
||||||
|
decimals: this.decimals,
|
||||||
|
lineWidth: 1,
|
||||||
|
lineColor: '#37383b',
|
||||||
|
lineType: [3, 3],
|
||||||
|
startSymbol: TimeSeriesChartShape.circle,
|
||||||
|
startSymbolSize: 5,
|
||||||
|
endSymbol: TimeSeriesChartShape.arrow,
|
||||||
|
endSymbolSize: 7,
|
||||||
|
showLabel: true,
|
||||||
|
labelPosition: 'insideEndTop',
|
||||||
|
labelColor: '#37383b',
|
||||||
|
additionalLabelOption: {
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.56)',
|
||||||
|
padding: [4, 5],
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
value: item
|
||||||
|
} as DeepPartial<TimeSeriesChartThreshold>));
|
||||||
|
const settings: DeepPartial<TimeSeriesChartSettings> = {
|
||||||
|
dataZoom: this.settings.dataZoom,
|
||||||
|
thresholds,
|
||||||
|
yAxes: {
|
||||||
|
default: {
|
||||||
|
show: true,
|
||||||
|
showLine: false,
|
||||||
|
showTicks: false,
|
||||||
|
showTickLabels: true,
|
||||||
|
showSplitLines: true,
|
||||||
|
decimals: this.decimals,
|
||||||
|
units: this.units
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
show: true,
|
||||||
|
showLine: true,
|
||||||
|
showTicks: true,
|
||||||
|
showTickLabels: true,
|
||||||
|
showSplitLines: false
|
||||||
|
},
|
||||||
|
visualMapSettings: {
|
||||||
|
outOfRangeColor: this.settings.outOfRangeColor,
|
||||||
|
pieces: this.rangeItems.map(item => item.piece)
|
||||||
|
},
|
||||||
|
showTooltip: this.settings.showTooltip,
|
||||||
|
tooltipValueFont: this.settings.tooltipValueFont,
|
||||||
|
tooltipValueColor: this.settings.tooltipValueColor,
|
||||||
|
tooltipShowDate: this.settings.tooltipShowDate,
|
||||||
|
tooltipDateInterval: this.settings.tooltipDateInterval,
|
||||||
|
tooltipDateFormat: this.settings.tooltipDateFormat,
|
||||||
|
tooltipDateFont: this.settings.tooltipDateFont,
|
||||||
|
tooltipDateColor: this.settings.tooltipDateColor,
|
||||||
|
tooltipBackgroundColor: this.settings.tooltipBackgroundColor,
|
||||||
|
tooltipBackgroundBlur: this.settings.tooltipBackgroundBlur,
|
||||||
|
};
|
||||||
|
this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, settings, this.chartShape.nativeElement, this.renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (this.shapeResize$) {
|
if (this.timeSeriesChart) {
|
||||||
this.shapeResize$.disconnect();
|
this.timeSeriesChart.destroy();
|
||||||
}
|
|
||||||
if (this.rangeChart) {
|
|
||||||
this.rangeChart.dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onInit() {
|
public onInit() {
|
||||||
const borderRadius = this.ctx.$widgetElement.css('borderRadius');
|
const borderRadius = this.ctx.$widgetElement.css('borderRadius');
|
||||||
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
|
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
|
||||||
if (this.chartShape) {
|
|
||||||
this.drawChart();
|
|
||||||
} else {
|
|
||||||
this.drawChartPending = true;
|
|
||||||
}
|
|
||||||
this.cd.detectChanges();
|
this.cd.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDataUpdated() {
|
public onDataUpdated() {
|
||||||
if (this.rangeChart) {
|
if (this.timeSeriesChart) {
|
||||||
this.rangeChart.setOption({
|
this.timeSeriesChart.update();
|
||||||
xAxis: {
|
|
||||||
min: this.ctx.defaultSubscription.timeWindow.minTime,
|
|
||||||
max: this.ctx.defaultSubscription.timeWindow.maxTime,
|
|
||||||
tbTimeWindow: this.ctx.defaultSubscription.timeWindow
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{data: this.ctx.data?.length ? toNamedData(this.ctx.data[0].data) : []}
|
|
||||||
],
|
|
||||||
visualMap: {
|
|
||||||
selected: this.selectedRanges
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleRangeItem(item: RangeItem) {
|
public toggleRangeItem(item: RangeItem) {
|
||||||
item.enabled = !item.enabled;
|
item.enabled = !item.enabled;
|
||||||
this.selectedRanges[item.index] = item.enabled;
|
this.timeSeriesChart.toggleVisualMapRange(item.index);
|
||||||
this.rangeChart.dispatchAction({
|
|
||||||
type: 'selectDataRange',
|
|
||||||
selected: this.selectedRanges
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private drawChart() {
|
|
||||||
echartsModule.init();
|
|
||||||
const dataKey = getDataKey(this.ctx.datasources);
|
|
||||||
this.rangeChart = echarts.init(this.chartShape.nativeElement, null, {
|
|
||||||
renderer: 'canvas',
|
|
||||||
});
|
|
||||||
this.rangeChartOptions = {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
confine: true,
|
|
||||||
appendTo: 'body',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'shadow'
|
|
||||||
},
|
|
||||||
formatter: (params: CallbackDataParams[]) =>
|
|
||||||
this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat,
|
|
||||||
this.settings, params, this.decimals, this.units, 0, null,
|
|
||||||
this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined,
|
|
||||||
padding: [8, 12],
|
|
||||||
backgroundColor: this.settings.tooltipBackgroundColor,
|
|
||||||
borderWidth: 0,
|
|
||||||
extraCssText: `line-height: 1; backdrop-filter: blur(${this.settings.tooltipBackgroundBlur}px);`
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
containLabel: true,
|
|
||||||
top: '30',
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: this.settings.dataZoom ? 60 : 0
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'time',
|
|
||||||
axisTick: {
|
|
||||||
show: true
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
hideOverlap: true,
|
|
||||||
fontSize: 10
|
|
||||||
},
|
|
||||||
axisLine: {
|
|
||||||
onZero: false
|
|
||||||
},
|
|
||||||
min: this.ctx.defaultSubscription.timeWindow.minTime,
|
|
||||||
max: this.ctx.defaultSubscription.timeWindow.maxTime,
|
|
||||||
bandWidthCalculator: timeAxisBandWidthCalculator
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
axisLabel: {
|
|
||||||
formatter: (value: any) => formatValue(value, this.decimals, this.units, false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
type: 'line',
|
|
||||||
name: dataKey?.label,
|
|
||||||
smooth: false,
|
|
||||||
showSymbol: false,
|
|
||||||
animation: true,
|
|
||||||
areaStyle: this.settings.fillArea ? {} : undefined,
|
|
||||||
data: this.ctx.data?.length ? toNamedData(this.ctx.data[0].data) : [],
|
|
||||||
markLine: this.rangeItems.length ? {
|
|
||||||
animation: true,
|
|
||||||
symbol: ['circle', 'arrow'],
|
|
||||||
symbolSize: [5, 7],
|
|
||||||
lineStyle: {
|
|
||||||
width: 1,
|
|
||||||
type: [3, 3],
|
|
||||||
color: '#37383b'
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
position: 'insideEndTop',
|
|
||||||
color: '#37383b',
|
|
||||||
backgroundColor: 'rgba(255,255,255,0.56)',
|
|
||||||
padding: [4, 5],
|
|
||||||
borderRadius: 4,
|
|
||||||
formatter: params => formatValue(params.value, this.decimals, this.units, false)
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
disabled: true
|
|
||||||
},
|
|
||||||
data: getMarkPoints(this.rangeItems).map(point => ({ yAxis: point }))
|
|
||||||
} : undefined
|
|
||||||
}],
|
|
||||||
dataZoom: [
|
|
||||||
{
|
|
||||||
type: 'inside',
|
|
||||||
disabled: !this.settings.dataZoom
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'slider',
|
|
||||||
show: this.settings.dataZoom,
|
|
||||||
showDetail: false,
|
|
||||||
right: 10
|
|
||||||
}
|
|
||||||
],
|
|
||||||
visualMap: {
|
|
||||||
show: false,
|
|
||||||
type: 'piecewise',
|
|
||||||
selected: this.selectedRanges,
|
|
||||||
dimension: 1,
|
|
||||||
pieces: this.rangeItems.map(item => item.piece),
|
|
||||||
outOfRange: {
|
|
||||||
color: this.settings.outOfRangeColor
|
|
||||||
},
|
|
||||||
inRange: !this.rangeItems.length ? {
|
|
||||||
color: this.settings.outOfRangeColor
|
|
||||||
} : undefined
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(this.rangeChartOptions.xAxis as any).tbTimeWindow = this.ctx.defaultSubscription.timeWindow;
|
|
||||||
|
|
||||||
this.rangeChart.setOption(this.rangeChartOptions);
|
|
||||||
|
|
||||||
this.shapeResize$ = new ResizeObserver(() => {
|
|
||||||
this.onResize();
|
|
||||||
});
|
|
||||||
this.shapeResize$.observe(this.chartShape.nativeElement);
|
|
||||||
this.onResize();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onResize() {
|
|
||||||
const width = this.rangeChart.getWidth();
|
|
||||||
const height = this.rangeChart.getHeight();
|
|
||||||
const shapeWidth = this.chartShape.nativeElement.offsetWidth;
|
|
||||||
const shapeHeight = this.chartShape.nativeElement.offsetHeight;
|
|
||||||
if (width !== shapeWidth || height !== shapeHeight) {
|
|
||||||
this.rangeChart.resize();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,12 +31,17 @@ import {
|
|||||||
textStyle,
|
textStyle,
|
||||||
tsToFormatTimeUnit
|
tsToFormatTimeUnit
|
||||||
} from '@shared/models/widget-settings.models';
|
} from '@shared/models/widget-settings.models';
|
||||||
import { LabelLayoutOptionCallback, XAXisOption, YAXisOption } from 'echarts/types/dist/shared';
|
import {
|
||||||
|
LabelLayoutOptionCallback,
|
||||||
|
VisualMapComponentOption,
|
||||||
|
XAXisOption,
|
||||||
|
YAXisOption
|
||||||
|
} from 'echarts/types/dist/shared';
|
||||||
import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
|
import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
|
||||||
import {
|
import {
|
||||||
formatValue,
|
formatValue,
|
||||||
isDefinedAndNotNull,
|
isDefinedAndNotNull,
|
||||||
isFunction,
|
isFunction, isNumber,
|
||||||
isNumeric,
|
isNumeric,
|
||||||
isUndefined,
|
isUndefined,
|
||||||
isUndefinedOrNull,
|
isUndefinedOrNull,
|
||||||
@ -417,7 +422,7 @@ export interface TimeSeriesChartThreshold {
|
|||||||
units?: string;
|
units?: string;
|
||||||
decimals?: number;
|
decimals?: number;
|
||||||
lineColor: string;
|
lineColor: string;
|
||||||
lineType: TimeSeriesChartLineType;
|
lineType: TimeSeriesChartLineType | number | number[];
|
||||||
lineWidth: number;
|
lineWidth: number;
|
||||||
startSymbol: TimeSeriesChartShape;
|
startSymbol: TimeSeriesChartShape;
|
||||||
startSymbolSize: number;
|
startSymbolSize: number;
|
||||||
@ -427,6 +432,7 @@ export interface TimeSeriesChartThreshold {
|
|||||||
labelPosition: ThresholdLabelPosition;
|
labelPosition: ThresholdLabelPosition;
|
||||||
labelFont: Font;
|
labelFont: Font;
|
||||||
labelColor: string;
|
labelColor: string;
|
||||||
|
additionalLabelOption?: {[key: string]: any};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const timeSeriesChartThresholdValid = (threshold: TimeSeriesChartThreshold): boolean => {
|
export const timeSeriesChartThresholdValid = (threshold: TimeSeriesChartThreshold): boolean => {
|
||||||
@ -567,6 +573,39 @@ export interface TimeSeriesChartAnimationSettings {
|
|||||||
animationDelayUpdate: number;
|
animationDelayUpdate: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TimeSeriesChartVisualMapPiece {
|
||||||
|
lt?: number;
|
||||||
|
gt?: number;
|
||||||
|
lte?: number;
|
||||||
|
gte?: number;
|
||||||
|
value?: number;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createTimeSeriesChartVisualMapPiece = (color: string, from?: number, to?: number): TimeSeriesChartVisualMapPiece => {
|
||||||
|
const piece: TimeSeriesChartVisualMapPiece = {
|
||||||
|
color
|
||||||
|
};
|
||||||
|
if (isNumber(from) && isNumber(to)) {
|
||||||
|
if (from === to) {
|
||||||
|
piece.value = from;
|
||||||
|
} else {
|
||||||
|
piece.gte = from;
|
||||||
|
piece.lt = to;
|
||||||
|
}
|
||||||
|
} else if (isNumber(from)) {
|
||||||
|
piece.gte = from;
|
||||||
|
} else if (isNumber(to)) {
|
||||||
|
piece.lt = to;
|
||||||
|
}
|
||||||
|
return piece;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface TimeSeriesChartVisualMapSettings {
|
||||||
|
outOfRangeColor: string;
|
||||||
|
pieces: TimeSeriesChartVisualMapPiece[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
|
export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
|
||||||
thresholds: TimeSeriesChartThreshold[];
|
thresholds: TimeSeriesChartThreshold[];
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
@ -577,6 +616,7 @@ export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
|
|||||||
animation: TimeSeriesChartAnimationSettings;
|
animation: TimeSeriesChartAnimationSettings;
|
||||||
barWidthSettings: TimeSeriesChartBarWidthSettings;
|
barWidthSettings: TimeSeriesChartBarWidthSettings;
|
||||||
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
|
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
|
||||||
|
visualMapSettings?: TimeSeriesChartVisualMapSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
|
export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
|
||||||
@ -675,7 +715,7 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface SeriesFillSettings {
|
export interface SeriesFillSettings {
|
||||||
type: SeriesFillType;
|
type: SeriesFillType | 'default';
|
||||||
opacity: number;
|
opacity: number;
|
||||||
gradient: {
|
gradient: {
|
||||||
start: number;
|
start: number;
|
||||||
@ -960,7 +1000,7 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettin
|
|||||||
fontFamily: xAxisTickLabelStyle.fontFamily,
|
fontFamily: xAxisTickLabelStyle.fontFamily,
|
||||||
fontSize: xAxisTickLabelStyle.fontSize,
|
fontSize: xAxisTickLabelStyle.fontSize,
|
||||||
hideOverlap: true,
|
hideOverlap: true,
|
||||||
formatter: (value: number, index: number, extra: {level: number}) => {
|
formatter: (value: number, _index: number, extra: {level: number}) => {
|
||||||
const unit = tsToFormatTimeUnit(value);
|
const unit = tsToFormatTimeUnit(value);
|
||||||
const format = ticksFormat[unit];
|
const format = ticksFormat[unit];
|
||||||
const formatted = datePipe.transform(value, format);
|
const formatted = datePipe.transform(value, format);
|
||||||
@ -990,6 +1030,21 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettin
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createTimeSeriesVisualMapOption = (settings: TimeSeriesChartVisualMapSettings,
|
||||||
|
selectedRanges: {[key: number]: boolean}): VisualMapComponentOption => ({
|
||||||
|
show: false,
|
||||||
|
type: 'piecewise',
|
||||||
|
selected: selectedRanges,
|
||||||
|
dimension: 1,
|
||||||
|
pieces: settings.pieces,
|
||||||
|
outOfRange: {
|
||||||
|
color: settings.outOfRangeColor
|
||||||
|
},
|
||||||
|
inRange: !settings.pieces.length ? {
|
||||||
|
color: settings.outOfRangeColor
|
||||||
|
} : undefined
|
||||||
|
});
|
||||||
|
|
||||||
export const generateChartData = (dataItems: TimeSeriesChartDataItem[],
|
export const generateChartData = (dataItems: TimeSeriesChartDataItem[],
|
||||||
thresholdItems: TimeSeriesChartThresholdItem[],
|
thresholdItems: TimeSeriesChartThresholdItem[],
|
||||||
stack: boolean,
|
stack: boolean,
|
||||||
@ -1072,6 +1127,9 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (item.settings.additionalLabelOption) {
|
||||||
|
seriesOption.markLine.label = {...seriesOption.markLine.label, ...item.settings.additionalLabelOption};
|
||||||
|
}
|
||||||
item.option = seriesOption;
|
item.option = seriesOption;
|
||||||
}
|
}
|
||||||
seriesOption.markLine.data = [];
|
seriesOption.markLine.data = [];
|
||||||
@ -1147,7 +1205,7 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[],
|
|||||||
|
|
||||||
export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings,
|
export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings,
|
||||||
yAxisList: TimeSeriesChartYAxis[],
|
yAxisList: TimeSeriesChartYAxis[],
|
||||||
dataItems: TimeSeriesChartDataItem[], thresholdDataItems: TimeSeriesChartThresholdItem[],
|
dataItems: TimeSeriesChartDataItem[],
|
||||||
darkMode: boolean): EChartsOption => {
|
darkMode: boolean): EChartsOption => {
|
||||||
options.darkMode = darkMode;
|
options.darkMode = darkMode;
|
||||||
if (Array.isArray(options.yAxis)) {
|
if (Array.isArray(options.yAxis)) {
|
||||||
@ -1246,7 +1304,7 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
|
|||||||
lineSeriesOption.areaStyle = {};
|
lineSeriesOption.areaStyle = {};
|
||||||
if (lineSettings.fillAreaSettings.type === SeriesFillType.opacity) {
|
if (lineSettings.fillAreaSettings.type === SeriesFillType.opacity) {
|
||||||
lineSeriesOption.areaStyle.opacity = lineSettings.fillAreaSettings.opacity;
|
lineSeriesOption.areaStyle.opacity = lineSettings.fillAreaSettings.opacity;
|
||||||
} else {
|
} else if (lineSettings.fillAreaSettings.type === SeriesFillType.gradient) {
|
||||||
lineSeriesOption.areaStyle.opacity = 1;
|
lineSeriesOption.areaStyle.opacity = 1;
|
||||||
lineSeriesOption.areaStyle.color = createLinearOpacityGradient(seriesColor, lineSettings.fillAreaSettings.gradient);
|
lineSeriesOption.areaStyle.color = createLinearOpacityGradient(seriesColor, lineSettings.fillAreaSettings.gradient);
|
||||||
}
|
}
|
||||||
@ -1264,7 +1322,7 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
|
|||||||
borderWidth: barSettings.showBorder ? barSettings.borderWidth : 0,
|
borderWidth: barSettings.showBorder ? barSettings.borderWidth : 0,
|
||||||
borderRadius: barSettings.borderRadius
|
borderRadius: barSettings.borderRadius
|
||||||
};
|
};
|
||||||
if (barSettings.backgroundSettings.type === SeriesFillType.none) {
|
if (barSettings.backgroundSettings.type === SeriesFillType.none || barSettings.backgroundSettings.type === 'default') {
|
||||||
barVisualSettings.color = seriesColor;
|
barVisualSettings.color = seriesColor;
|
||||||
} else if (barSettings.backgroundSettings.type === SeriesFillType.opacity) {
|
} else if (barSettings.backgroundSettings.type === SeriesFillType.opacity) {
|
||||||
barVisualSettings.color = tinycolor(seriesColor).setAlpha(barSettings.backgroundSettings.opacity).toRgbString();
|
barVisualSettings.color = tinycolor(seriesColor).setAlpha(barSettings.backgroundSettings.opacity).toRgbString();
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { WidgetContext } from '@home/models/widget-component.models';
|
|||||||
import {
|
import {
|
||||||
AxisPosition,
|
AxisPosition,
|
||||||
calculateThresholdsOffset,
|
calculateThresholdsOffset,
|
||||||
|
createTimeSeriesVisualMapOption,
|
||||||
createTimeSeriesXAxisOption,
|
createTimeSeriesXAxisOption,
|
||||||
createTimeSeriesYAxis,
|
createTimeSeriesYAxis,
|
||||||
defaultTimeSeriesChartYAxisSettings,
|
defaultTimeSeriesChartYAxisSettings,
|
||||||
@ -51,7 +52,8 @@ import {
|
|||||||
EChartsOption,
|
EChartsOption,
|
||||||
echartsTooltipFormatter,
|
echartsTooltipFormatter,
|
||||||
EChartsTooltipTrigger,
|
EChartsTooltipTrigger,
|
||||||
getAxisExtent, getFocusedSeriesIndex,
|
getAxisExtent,
|
||||||
|
getFocusedSeriesIndex,
|
||||||
measureXAxisNameHeight,
|
measureXAxisNameHeight,
|
||||||
measureYAxisNameWidth,
|
measureYAxisNameWidth,
|
||||||
toNamedData
|
toNamedData
|
||||||
@ -60,7 +62,7 @@ import { DateFormatProcessor } from '@shared/models/widget-settings.models';
|
|||||||
import { isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils';
|
import { isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils';
|
||||||
import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models';
|
import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models';
|
||||||
import * as echarts from 'echarts/core';
|
import * as echarts from 'echarts/core';
|
||||||
import { CallbackDataParams } from 'echarts/types/dist/shared';
|
import { CallbackDataParams, PiecewiseVisualMapOption } from 'echarts/types/dist/shared';
|
||||||
import { Renderer2 } from '@angular/core';
|
import { Renderer2 } from '@angular/core';
|
||||||
import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
|
import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
@ -107,6 +109,9 @@ export class TbTimeSeriesChart {
|
|||||||
private dataItems: TimeSeriesChartDataItem[] = [];
|
private dataItems: TimeSeriesChartDataItem[] = [];
|
||||||
private thresholdItems: TimeSeriesChartThresholdItem[] = [];
|
private thresholdItems: TimeSeriesChartThresholdItem[] = [];
|
||||||
|
|
||||||
|
private hasVisualMap = false;
|
||||||
|
private visualMapSelectedRanges: {[key: number]: boolean};
|
||||||
|
|
||||||
private timeSeriesChart: ECharts;
|
private timeSeriesChart: ECharts;
|
||||||
private timeSeriesChartOptions: EChartsOption;
|
private timeSeriesChartOptions: EChartsOption;
|
||||||
|
|
||||||
@ -145,6 +150,7 @@ export class TbTimeSeriesChart {
|
|||||||
this.setupYAxes();
|
this.setupYAxes();
|
||||||
this.setupData();
|
this.setupData();
|
||||||
this.setupThresholds();
|
this.setupThresholds();
|
||||||
|
this.setupVisualMap();
|
||||||
if (this.settings.showTooltip && this.settings.tooltipShowDate) {
|
if (this.settings.showTooltip && this.settings.tooltipShowDate) {
|
||||||
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat);
|
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat);
|
||||||
}
|
}
|
||||||
@ -186,6 +192,9 @@ export class TbTimeSeriesChart {
|
|||||||
} else {
|
} else {
|
||||||
this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'shadow';
|
this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'shadow';
|
||||||
}
|
}
|
||||||
|
if (this.hasVisualMap) {
|
||||||
|
(this.timeSeriesChartOptions.visualMap as PiecewiseVisualMapOption).selected = this.visualMapSelectedRanges;
|
||||||
|
}
|
||||||
this.barRenderSharedContext.timeInterval = this.ctx.timeWindow.interval;
|
this.barRenderSharedContext.timeInterval = this.ctx.timeWindow.interval;
|
||||||
this.updateSeriesData(true);
|
this.updateSeriesData(true);
|
||||||
if (this.highlightedDataKey) {
|
if (this.highlightedDataKey) {
|
||||||
@ -263,6 +272,16 @@ export class TbTimeSeriesChart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toggleVisualMapRange(index: number): void {
|
||||||
|
if (this.hasVisualMap) {
|
||||||
|
this.visualMapSelectedRanges[index] = !this.visualMapSelectedRanges[index];
|
||||||
|
this.timeSeriesChart.dispatchAction({
|
||||||
|
type: 'selectDataRange',
|
||||||
|
selected: this.visualMapSelectedRanges
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
if (this.shapeResize$) {
|
if (this.shapeResize$) {
|
||||||
this.shapeResize$.disconnect();
|
this.shapeResize$.disconnect();
|
||||||
@ -284,8 +303,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,
|
this.settings, this.yAxisList, this.dataItems, darkMode);
|
||||||
this.thresholdItems, darkMode);
|
|
||||||
this.timeSeriesChart.setOption(this.timeSeriesChartOptions);
|
this.timeSeriesChart.setOption(this.timeSeriesChartOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -431,6 +449,15 @@ export class TbTimeSeriesChart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setupVisualMap(): void {
|
||||||
|
if (this.settings.visualMapSettings?.pieces && this.settings.visualMapSettings?.pieces.length) {
|
||||||
|
this.hasVisualMap = true;
|
||||||
|
this.visualMapSelectedRanges = {};
|
||||||
|
this.settings.visualMapSettings.pieces.forEach((_val, index) => {
|
||||||
|
this.visualMapSelectedRanges[index] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private nextComponentId(): string {
|
private nextComponentId(): string {
|
||||||
return (this.componentIndexCounter++) + '';
|
return (this.componentIndexCounter++) + '';
|
||||||
@ -536,6 +563,10 @@ export class TbTimeSeriesChart {
|
|||||||
animationEasingUpdate: this.settings.animation.animationEasingUpdate,
|
animationEasingUpdate: this.settings.animation.animationEasingUpdate,
|
||||||
animationDelayUpdate: this.settings.animation.animationDelayUpdate
|
animationDelayUpdate: this.settings.animation.animationDelayUpdate
|
||||||
};
|
};
|
||||||
|
if (this.hasVisualMap) {
|
||||||
|
this.timeSeriesChartOptions.visualMap =
|
||||||
|
createTimeSeriesVisualMapOption(this.settings.visualMapSettings, this.visualMapSelectedRanges);
|
||||||
|
}
|
||||||
|
|
||||||
this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow;
|
this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user