|
|
|
@ -32,48 +32,38 @@ import { |
|
|
|
backgroundStyle, |
|
|
|
ColorRange, |
|
|
|
ComponentStyle, |
|
|
|
DateFormatProcessor, |
|
|
|
filterIncludingColorRanges, |
|
|
|
getDataKey, |
|
|
|
overlayStyle, |
|
|
|
sortedColorRange, |
|
|
|
textStyle |
|
|
|
} from '@shared/models/widget-settings.models'; |
|
|
|
import { ResizeObserver } from '@juggle/resize-observer'; |
|
|
|
import * as echarts from 'echarts/core'; |
|
|
|
import { formatValue, isDefinedAndNotNull, isNumber } from '@core/utils'; |
|
|
|
import { isDefinedAndNotNull, isNumber } from '@core/utils'; |
|
|
|
import { rangeChartDefaultSettings, RangeChartWidgetSettings } from './range-chart-widget.models'; |
|
|
|
import { Observable } from 'rxjs'; |
|
|
|
import { ImagePipe } from '@shared/pipe/image.pipe'; |
|
|
|
import { DomSanitizer } from '@angular/platform-browser'; |
|
|
|
import { DeepPartial } from '@shared/models/common'; |
|
|
|
import { |
|
|
|
ECharts, |
|
|
|
echartsModule, |
|
|
|
EChartsOption, |
|
|
|
echartsTooltipFormatter, timeAxisBandWidthCalculator, |
|
|
|
toNamedData |
|
|
|
} from '@home/components/widget/lib/chart/echarts-widget.models'; |
|
|
|
import { CallbackDataParams } from 'echarts/types/dist/shared'; |
|
|
|
import { AggregationType } from '@shared/models/time/time.models'; |
|
|
|
|
|
|
|
interface VisualPiece { |
|
|
|
lt?: number; |
|
|
|
gt?: number; |
|
|
|
lte?: number; |
|
|
|
gte?: number; |
|
|
|
value?: number; |
|
|
|
color?: string; |
|
|
|
} |
|
|
|
createTimeSeriesChartVisualMapPiece, |
|
|
|
SeriesFillType, |
|
|
|
TimeSeriesChartKeySettings, |
|
|
|
TimeSeriesChartSettings, |
|
|
|
TimeSeriesChartShape, |
|
|
|
TimeSeriesChartThreshold, |
|
|
|
TimeSeriesChartThresholdType, TimeSeriesChartVisualMapPiece |
|
|
|
} from '@home/components/widget/lib/chart/time-series-chart.models'; |
|
|
|
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; |
|
|
|
|
|
|
|
interface RangeItem { |
|
|
|
index: number; |
|
|
|
from?: number; |
|
|
|
to?: number; |
|
|
|
piece: VisualPiece; |
|
|
|
color: string; |
|
|
|
label: string; |
|
|
|
visible: boolean; |
|
|
|
enabled: boolean; |
|
|
|
piece: TimeSeriesChartVisualMapPiece; |
|
|
|
} |
|
|
|
|
|
|
|
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 rangeItems: RangeItem[] = []; |
|
|
|
let counter = 0; |
|
|
|
@ -134,7 +105,7 @@ const toRangeItems = (colorRanges: Array<ColorRange>): RangeItem[] => { |
|
|
|
from, |
|
|
|
to, |
|
|
|
label: rangeItemLabel(from, to), |
|
|
|
piece: toVisualPiece(range.color, from, to) |
|
|
|
piece: createTimeSeriesChartVisualMapPiece(range.color, from, to) |
|
|
|
} |
|
|
|
); |
|
|
|
if (!isNumber(from) || !isNumber(to)) { |
|
|
|
@ -198,23 +169,12 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn |
|
|
|
disabledLegendLabelStyle: ComponentStyle; |
|
|
|
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 units = ''; |
|
|
|
|
|
|
|
private drawChartPending = false; |
|
|
|
private rangeChart: ECharts; |
|
|
|
private rangeChartOptions: EChartsOption; |
|
|
|
private selectedRanges: {[key: number]: boolean} = {}; |
|
|
|
private rangeItems: RangeItem[]; |
|
|
|
|
|
|
|
private tooltipDateFormat: DateFormatProcessor; |
|
|
|
private timeSeriesChart: TbTimeSeriesChart; |
|
|
|
|
|
|
|
constructor(private imagePipe: ImagePipe, |
|
|
|
private sanitizer: DomSanitizer, |
|
|
|
@ -235,16 +195,25 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn |
|
|
|
if (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.overlayStyle = overlayStyle(this.settings.background.overlay); |
|
|
|
|
|
|
|
this.rangeItems = toRangeItems(this.settings.rangeColors); |
|
|
|
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; |
|
|
|
|
|
|
|
@ -254,193 +223,90 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn |
|
|
|
this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont); |
|
|
|
this.legendLabelStyle.color = this.settings.legendLabelColor; |
|
|
|
} |
|
|
|
|
|
|
|
if (this.settings.showTooltip && this.settings.tooltipShowDate) { |
|
|
|
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
ngAfterViewInit() { |
|
|
|
if (this.drawChartPending) { |
|
|
|
this.drawChart(); |
|
|
|
} |
|
|
|
const thresholds: DeepPartial<TimeSeriesChartThreshold>[] = getMarkPoints(this.rangeItems).map(item => ({ |
|
|
|
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() { |
|
|
|
if (this.shapeResize$) { |
|
|
|
this.shapeResize$.disconnect(); |
|
|
|
} |
|
|
|
if (this.rangeChart) { |
|
|
|
this.rangeChart.dispose(); |
|
|
|
if (this.timeSeriesChart) { |
|
|
|
this.timeSeriesChart.destroy(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public onInit() { |
|
|
|
const borderRadius = this.ctx.$widgetElement.css('borderRadius'); |
|
|
|
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; |
|
|
|
if (this.chartShape) { |
|
|
|
this.drawChart(); |
|
|
|
} else { |
|
|
|
this.drawChartPending = true; |
|
|
|
} |
|
|
|
this.cd.detectChanges(); |
|
|
|
} |
|
|
|
|
|
|
|
public onDataUpdated() { |
|
|
|
if (this.rangeChart) { |
|
|
|
this.rangeChart.setOption({ |
|
|
|
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 |
|
|
|
} |
|
|
|
}); |
|
|
|
if (this.timeSeriesChart) { |
|
|
|
this.timeSeriesChart.update(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public toggleRangeItem(item: RangeItem) { |
|
|
|
item.enabled = !item.enabled; |
|
|
|
this.selectedRanges[item.index] = item.enabled; |
|
|
|
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(); |
|
|
|
} |
|
|
|
this.timeSeriesChart.toggleVisualMapRange(item.index); |
|
|
|
} |
|
|
|
} |
|
|
|
|