Browse Source

UI: Refactor bar chart with labels widget to use time series chart class.

pull/10473/head
Igor Kulikov 3 years ago
parent
commit
7900d5f29c
  1. 12
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html
  2. 393
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
  3. 107
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts
  4. 43
      ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts
  5. 27
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts
  6. 67
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  7. 9
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts

12
ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html

@ -22,13 +22,13 @@
<div #chartShape class="tb-bar-chart-shape">
</div>
<div *ngIf="showLegend" class="tb-bar-chart-legend">
<div class="tb-bar-chart-legend-item" *ngFor="let legendItem of legendItems"
(mouseenter)="onLegendItemEnter(legendItem)"
(mouseleave)="onLegendItemLeave(legendItem)"
(click)="toggleLegendItem(legendItem)">
<div class="tb-bar-chart-legend-item" *ngFor="let legendKey of legendKeys"
(mouseenter)="onLegendKeyEnter(legendKey)"
(mouseleave)="onLegendKeyLeave(legendKey)"
(click)="toggleLegendKey(legendKey)">
<div class="tb-bar-chart-legend-item-label">
<div class="tb-bar-chart-legend-item-label-circle" [style]="{background: legendItem.enabled ? legendItem.color : null}"></div>
<div [style]="legendItem.enabled ? legendLabelStyle : disabledLegendLabelStyle">{{ legendItem.label }}</div>
<div class="tb-bar-chart-legend-item-label-circle" [style]="{background: !legendKey.hidden ? legendKey.color : null}"></div>
<div [style]="!legendKey.hidden ? legendLabelStyle : disabledLegendLabelStyle">{{ legendKey.label }}</div>
</div>
</div>
</div>

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

@ -28,45 +28,18 @@ import {
ViewEncapsulation
} from '@angular/core';
import { WidgetContext } from '@home/models/widget-component.models';
import {
backgroundStyle,
ComponentStyle,
DateFormatProcessor,
overlayStyle,
textStyle
} from '@shared/models/widget-settings.models';
import { ResizeObserver } from '@juggle/resize-observer';
import { formatValue } from '@core/utils';
import { backgroundStyle, ComponentStyle, overlayStyle, textStyle } from '@shared/models/widget-settings.models';
import { Observable } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser';
import {
barChartWithLabelsDefaultSettings,
barChartWithLabelsTimeSeriesKeySettings,
barChartWithLabelsTimeSeriesSettings,
BarChartWithLabelsWidgetSettings
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models';
import * as echarts from 'echarts/core';
import { CustomSeriesOption } from 'echarts/charts';
import { CallbackDataParams, CustomSeriesRenderItem, LabelLayoutOptionCallback } from 'echarts/types/dist/shared';
import {
ECharts,
echartsModule,
EChartsOption,
EChartsSeriesItem,
echartsTooltipFormatter, timeAxisBandWidthCalculator,
toNamedData
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { AggregationType, IntervalMath } from '@shared/models/time/time.models';
type BarChartDataItem = EChartsSeriesItem;
interface BarChartLegendItem {
id: string;
color: string;
label: string;
enabled: boolean;
}
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
import { DataKey } from '@shared/models/widget.models';
@Component({
selector: 'tb-bar-chart-with-labels-widget',
@ -93,29 +66,11 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {};
legendItems: BarChartLegendItem[];
legendKeys: DataKey[];
legendLabelStyle: ComponentStyle;
disabledLegendLabelStyle: ComponentStyle;
private get noAggregation(): boolean {
return this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE;
}
private shapeResize$: ResizeObserver;
private decimals = 0;
private units = '';
private dataItems: BarChartDataItem[] = [];
private drawChartPending = false;
private barChart: ECharts;
private barChartOptions: EChartsOption;
private tooltipDateFormat: DateFormatProcessor;
private barRenderItem: CustomSeriesRenderItem;
private barLabelLayoutCallback: LabelLayoutOptionCallback;
private timeSeriesChart: TbTimeSeriesChart;
constructor(private imagePipe: ImagePipe,
private sanitizer: DomSanitizer,
@ -127,356 +82,62 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
this.ctx.$scope.barChartWidget = this;
this.settings = {...barChartWithLabelsDefaultSettings, ...this.ctx.settings};
this.decimals = this.ctx.decimals;
this.units = this.ctx.units;
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
this.overlayStyle = overlayStyle(this.settings.background.overlay);
this.showLegend = this.settings.showLegend;
if (this.showLegend) {
this.legendItems = [];
this.legendKeys = [];
this.legendClass = `legend-${this.settings.legendPosition}`;
this.legendLabelStyle = textStyle(this.settings.legendLabelFont);
this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont);
this.legendLabelStyle.color = this.settings.legendLabelColor;
}
let counter = 0;
if (this.ctx.datasources.length) {
for (const datasource of this.ctx.datasources) {
const dataKeys = datasource.dataKeys;
for (const dataKey of dataKeys) {
const id = counter++;
const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === dataKey) : null;
const namedData = datasourceData?.data ? toNamedData(datasourceData.data) : [];
this.dataItems.push({
id: id+'',
dataKey,
data: namedData,
enabled: true
});
if (this.showLegend) {
this.legendItems.push(
{
id: id+'',
label: dataKey.label,
color: dataKey.color,
enabled: true
}
);
}
const barSettings = barChartWithLabelsTimeSeriesKeySettings(this.settings, this.ctx.decimals);
for (const datasource of this.ctx.datasources) {
const dataKeys = datasource.dataKeys;
for (const dataKey of dataKeys) {
dataKey.settings = barSettings;
if (this.showLegend) {
this.legendKeys.push(dataKey);
}
}
}
if (this.settings.showTooltip && this.settings.tooltipShowDate) {
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat);
}
const barValueStyle: ComponentStyle = textStyle(this.settings.barValueFont);
delete barValueStyle.lineHeight;
barValueStyle.fontSize = this.settings.barValueFont.size;
barValueStyle.fill = this.settings.barValueColor;
const barLabelStyle: ComponentStyle = textStyle(this.settings.barLabelFont);
delete barLabelStyle.lineHeight;
barLabelStyle.fontSize = this.settings.barLabelFont.size;
barLabelStyle.fill = this.settings.barLabelColor;
this.barRenderItem = (params, api) => {
const time = api.value(0) as number;
let start = api.value(2) as number;
const end = api.value(3) as number;
let interval = end - start;
if (!start || !end || !interval) {
interval = IntervalMath.numberValue(this.ctx.timeWindow.interval);
start = time - interval / 2;
}
const enabledDataItems = this.dataItems.filter(d => d.enabled);
const barInterval = interval / (enabledDataItems.length + 1);
const intervalGap = barInterval / 2;
const index = enabledDataItems.findIndex(d => d.id === params.seriesId);
const value = api.value(1);
const startTime = start + intervalGap + barInterval * index;
const delta = barInterval;
const lowerLeft = api.coord([startTime, value >= 0 ? value : 0]);
const height = api.size([delta, value])[1];
const width = api.size([delta, 10])[0];
const coordSys: {x: number; y: number; width: number; height: number} = params.coordSys as any;
const rectShape = echarts.graphic.clipRectByRect({
x: lowerLeft[0],
y: lowerLeft[1],
width,
height
}, {
x: coordSys.x,
y: coordSys.y,
width: coordSys.width,
height: coordSys.height
});
const zeroPos = api.coord([0, 0]);
const labelParts: string[] = [];
if (this.settings.showBarValue) {
const labelValue = formatValue(value, this.decimals, '', false);
labelParts.push(`{value|${labelValue}}`);
}
if (this.settings.showBarLabel) {
labelParts.push(`{label|${params.seriesName}}`);
}
const barLabel = labelParts.join(' ');
return rectShape && {
type: 'rect',
id: time + '',
shape: rectShape,
style: {
fill: api.visual('color'),
text: barLabel,
textPosition: 'insideBottom',
textRotation: Math.PI / 2,
textDistance: 15,
textStrokeWidth: 0,
textAlign: 'left',
textVerticalAlign: 'middle',
rich: {
value: barValueStyle,
label: barLabelStyle
}
},
focus: 'series',
transition: 'all',
enterFrom: {
style: { opacity: 0 },
shape: { height: 0, y: zeroPos[1] }
}
};
};
this.barLabelLayoutCallback = (params) => {
if (params.rect.width - params.labelRect.width < 2) {
return {
y: '100000%',
};
} else {
return {
hideOverlap: true
};
}
};
}
ngAfterViewInit() {
if (this.drawChartPending) {
this.drawChart();
}
const settings = barChartWithLabelsTimeSeriesSettings(this.settings);
this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, settings, this.chartShape.nativeElement, this.renderer);
}
ngOnDestroy() {
if (this.shapeResize$) {
this.shapeResize$.disconnect();
}
if (this.barChart) {
this.barChart.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() {
for (const item of this.dataItems) {
const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === item.dataKey) : null;
item.data = datasourceData?.data ? toNamedData(datasourceData.data) : [];
}
if (this.barChart) {
(this.barChartOptions.xAxis as any).min = this.ctx.defaultSubscription.timeWindow.minTime;
(this.barChartOptions.xAxis as any).max = this.ctx.defaultSubscription.timeWindow.maxTime;
(this.barChartOptions.xAxis as any).tbTimeWindow = this.ctx.defaultSubscription.timeWindow;
this.barChartOptions.series = this.updateSeries();
this.barChart.setOption(this.barChartOptions);
if (this.timeSeriesChart) {
this.timeSeriesChart.update();
}
}
private updateSeries(): Array<CustomSeriesOption> {
const series: Array<CustomSeriesOption> = [];
for (const item of this.dataItems) {
if (item.enabled) {
const seriesOption: CustomSeriesOption = {
type: 'custom',
id: item.id,
name: item.dataKey.label,
color: item.dataKey.color,
data: item.data,
renderItem: this.barRenderItem,
labelLayout: this.barLabelLayoutCallback,
dimensions: [
{name: 'intervalStart', type: 'number'},
{name: 'intervalEnd', type: 'number'}
],
encode: {
intervalStart: 2,
intervalEnd: 3
}
};
series.push(seriesOption);
}
}
return series;
public onLegendKeyEnter(key: DataKey) {
this.timeSeriesChart.keyEnter(key);
}
public onLegendItemEnter(item: BarChartLegendItem) {
this.barChart.dispatchAction({
type: 'highlight',
seriesId: item.id
});
}
public onLegendItemLeave(item: BarChartLegendItem) {
this.barChart.dispatchAction({
type: 'downplay',
seriesId: item.id
});
}
public toggleLegendItem(item: BarChartLegendItem) {
const enable = !item.enabled;
const dataItem = this.dataItems.find(d => d.id === item.id);
if (dataItem) {
dataItem.enabled = enable;
if (!enable) {
this.barChart.dispatchAction({
type: 'downplay',
seriesId: item.id
});
}
this.barChartOptions.series = this.updateSeries();
this.barChart.setOption(this.barChartOptions, {replaceMerge: ['series']});
item.enabled = enable;
if (enable) {
this.barChart.dispatchAction({
type: 'highlight',
seriesId: item.id
});
}
}
public onLegendKeyLeave(key: DataKey) {
this.timeSeriesChart.keyLeave(key);
}
private drawChart() {
echartsModule.init();
this.barChart = echarts.init(this.chartShape.nativeElement, null, {
renderer: 'canvas',
});
this.barChartOptions = {
tooltip: {
trigger: 'axis',
confine: true,
appendTo: 'body',
axisPointer: {
type: 'shadow'
},
formatter: (params: CallbackDataParams[]) => {
if (this.settings.showTooltip) {
const focusedSeriesIndex = this.focusedSeriesIndex();
return echartsTooltipFormatter(this.renderer, this.tooltipDateFormat,
this.settings, params, this.decimals, this.units, focusedSeriesIndex, null,
this.noAggregation ? null : this.ctx.timeWindow.interval);
} else {
return 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: 0
},
xAxis: {
type: 'time',
scale: true,
axisTick: {
show: false
},
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)
}
}
};
(this.barChartOptions.xAxis as any).tbTimeWindow = this.ctx.defaultSubscription.timeWindow;
this.barChartOptions.series = this.updateSeries();
this.barChart.setOption(this.barChartOptions);
this.shapeResize$ = new ResizeObserver(() => {
this.onResize();
});
this.shapeResize$.observe(this.chartShape.nativeElement);
this.onResize();
}
private focusedSeriesIndex(): number {
let index = - 1;
const views: any[] = (this.barChart as any)._chartsViews;
if (views) {
const hasBlurredView = !!views.find(view => {
const graphicEls: any[] = view._data._graphicEls;
return !!graphicEls.find(el => el?.currentStates.includes('blur'));
});
if (hasBlurredView) {
const focusedView = views.find(view => {
const graphicEls: any[] = view._data._graphicEls;
return !!graphicEls.find(el => !el?.currentStates.includes('blur'));
});
if (focusedView) {
index = !!focusedView._model ?
focusedView._model.seriesIndex : (!!focusedView.__model ? focusedView.__model.seriesIndex : -1);
}
}
}
return index;
}
private onResize() {
const width = this.barChart.getWidth();
const height = this.barChart.getHeight();
const shapeWidth = this.chartShape.nativeElement.offsetWidth;
const shapeHeight = this.chartShape.nativeElement.offsetHeight;
if (width !== shapeWidth || height !== shapeHeight) {
this.barChart.resize();
}
public toggleLegendKey(key: DataKey) {
this.timeSeriesChart.toggleKey(key);
}
}

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

@ -14,9 +14,23 @@
/// limitations under the License.
///
import { BackgroundSettings, BackgroundType, customDateFormat, Font } from '@shared/models/widget-settings.models';
import {
BackgroundSettings,
BackgroundType,
ComponentStyle,
customDateFormat,
Font, textStyle
} from '@shared/models/widget-settings.models';
import { LegendPosition } from '@shared/models/widget.models';
import { EChartsTooltipWidgetSettings } from '@home/components/widget/lib/chart/echarts-widget.models';
import { DeepPartial } from '@shared/models/common';
import {
TimeSeriesChartKeySettings, TimeSeriesChartSeriesType,
TimeSeriesChartSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { CallbackDataParams, LabelLayoutOptionCallbackParams } from 'echarts/types/dist/shared';
import { formatValue } from '@core/utils';
import { LabelLayoutOption } from 'echarts/types/src/util/types';
export interface BarChartWithLabelsWidgetSettings extends EChartsTooltipWidgetSettings {
showBarLabel: boolean;
@ -98,3 +112,94 @@ export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings
}
}
};
export const barChartWithLabelsTimeSeriesSettings = (settings: BarChartWithLabelsWidgetSettings): DeepPartial<TimeSeriesChartSettings> => ({
dataZoom: false,
yAxes: {
default: {
show: true,
showLine: false,
showTicks: false,
showTickLabels: true,
showSplitLines: true,
}
},
xAxis: {
show: true,
showLine: true,
showTicks: false,
showTickLabels: true,
showSplitLines: false
},
barWidthSettings: {
barGap: 0,
intervalGap: 0.5
},
showTooltip: settings.showTooltip,
tooltipValueFont: settings.tooltipValueFont,
tooltipValueColor: settings.tooltipValueColor,
tooltipShowDate: settings.tooltipShowDate,
tooltipDateInterval: settings.tooltipDateInterval,
tooltipDateFormat: settings.tooltipDateFormat,
tooltipDateFont: settings.tooltipDateFont,
tooltipDateColor: settings.tooltipDateColor,
tooltipBackgroundColor: settings.tooltipBackgroundColor,
tooltipBackgroundBlur: settings.tooltipBackgroundBlur,
tooltipShowFocusedSeries: true
});
export const barChartWithLabelsTimeSeriesKeySettings = (settings: BarChartWithLabelsWidgetSettings,
decimals: number): DeepPartial<TimeSeriesChartKeySettings> => {
const barValueStyle: ComponentStyle = textStyle(settings.barValueFont);
delete barValueStyle.lineHeight;
barValueStyle.fontSize = settings.barValueFont.size;
barValueStyle.fill = settings.barValueColor;
const barLabelStyle: ComponentStyle = textStyle(settings.barLabelFont);
delete barLabelStyle.lineHeight;
barLabelStyle.fontSize = settings.barLabelFont.size;
barLabelStyle.fill = settings.barLabelColor;
return {
type: TimeSeriesChartSeriesType.bar,
barSettings: {
showBorder: false,
borderWidth: 0,
borderRadius: 0,
showLabel: settings.showBarLabel,
labelPosition: 'insideBottom',
labelFormatter: (params: CallbackDataParams): string => {
const labelParts: string[] = [];
if (settings.showBarValue) {
const labelValue = formatValue(params.value[1], decimals, '', false);
labelParts.push(`{value|${labelValue}}`);
}
if (settings.showBarLabel) {
labelParts.push(`{label|${params.seriesName}}`);
}
return labelParts.join(' ');
},
labelLayout: (params: LabelLayoutOptionCallbackParams): LabelLayoutOption => {
if (params.rect.width - params.labelRect.width < 2) {
return {
y: '100000%',
};
} else {
return {
hideOverlap: true
};
}
},
additionalLabelOption: {
textRotation: Math.PI / 2,
textDistance: 15,
textStrokeWidth: 0,
textAlign: 'left',
textVerticalAlign: 'middle',
rich: {
value: barValueStyle,
label: barLabelStyle
}
}
}
};
};

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

@ -266,6 +266,48 @@ export const getAxisExtent = (chart: ECharts, axisId: string): [number, number]
return [0,0];
};
let componentBlurredKey: string;
const isBlurred = (model: SeriesModel): boolean => {
if (!componentBlurredKey) {
const innerKeys = Object.keys(model).filter(k => k.startsWith('__ec_inner_'));
for (const k of innerKeys) {
const obj = model[k];
if (obj.hasOwnProperty('isBlured')) {
componentBlurredKey = k;
break;
}
}
}
if (componentBlurredKey) {
const obj = model[componentBlurredKey];
return !!obj?.isBlured;
} else {
return false;
}
};
export const getFocusedSeriesIndex = (chart: ECharts): number => {
const model: GlobalModel = (chart as any).getModel();
const models = model.queryComponents({mainType: 'series'});
if (models) {
let hasBlurred = false;
let notBlurredIndex = -1;
for (const _model of models) {
const seriesModel = _model as SeriesModel;
const blurred = isBlurred(seriesModel);
if (!blurred) {
notBlurredIndex = seriesModel.seriesIndex;
}
hasBlurred = blurred || hasBlurred;
}
if (hasBlurred) {
return notBlurredIndex;
}
}
return -1;
};
export const toNamedData = (data: DataSet): NamedDataSet => {
if (!data?.length) {
return [];
@ -304,6 +346,7 @@ export const tooltipTriggerTranslationMap = new Map<EChartsTooltipTrigger, strin
export interface EChartsTooltipWidgetSettings {
showTooltip: boolean;
tooltipTrigger?: EChartsTooltipTrigger;
tooltipShowFocusedSeries?: boolean;
tooltipValueFont: Font;
tooltipValueColor: string;
tooltipShowDate: boolean;

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

@ -22,7 +22,7 @@ import {
TimeSeriesChartNoAggregationBarWidthStrategy
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { CustomSeriesRenderItemParams } from 'echarts';
import { CustomSeriesRenderItemAPI, CustomSeriesRenderItemReturn } from 'echarts/types/dist/shared';
import { CallbackDataParams, CustomSeriesRenderItemAPI, CustomSeriesRenderItemReturn } from 'echarts/types/dist/shared';
import { isNumeric } from '@core/utils';
import * as echarts from 'echarts/core';
@ -34,6 +34,8 @@ export interface BarVisualSettings {
}
export interface BarRenderSharedContext {
barGap: number;
intervalGap: number;
timeInterval: Interval;
noAggregationBarWidthStrategy: TimeSeriesChartNoAggregationBarWidthStrategy;
noAggregationWidthRelative: boolean;
@ -47,6 +49,7 @@ export interface BarRenderContext {
noAggregation?: boolean;
visualSettings?: BarVisualSettings;
labelOption?: SeriesLabelOption;
additionalLabelOption?: {[key: string]: any};
barStackIndex?: number;
currentStackItems?: TimeSeriesChartDataItem[];
}
@ -77,10 +80,13 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C
start = time - interval / 2;
}
const gap = 0.3;
const barInterval = separateBar ? interval : interval / (renderCtx.barsCount + gap * (renderCtx.barsCount + 3));
const intervalGap = barInterval * gap * 2;
const barGap = barInterval * gap;
const barGapRatio = renderCtx.shared.barGap;
const intervalGapRatio = renderCtx.shared.intervalGap;
const barInterval = separateBar
? interval
: interval / (renderCtx.barsCount + barGapRatio * (renderCtx.barsCount - 1) + intervalGapRatio * 2);
const intervalGap = barInterval * intervalGapRatio;
const barGap = barInterval * barGapRatio;
const value = api.value(1);
const startTime = separateBar ? start : start + intervalGap + (barInterval + barGap) * renderCtx.barIndex;
const delta = barInterval;
@ -124,7 +130,7 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C
const zeroPos = api.coord([0, offset]);
const style: any = {
let style: any = {
fill: renderCtx.visualSettings.color,
stroke: renderCtx.visualSettings.borderColor,
lineWidth: renderCtx.visualSettings.borderWidth
@ -139,10 +145,17 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C
position = 'top';
}
}
style.text = (renderCtx.labelOption.formatter as LabelFormatterCallback)({value: [null, value]} as any);
style.text = (renderCtx.labelOption.formatter as LabelFormatterCallback)(
{
seriesName: params.seriesName,
value: [null, value]
} as CallbackDataParams);
style.textDistance = 5;
style.textPosition = position;
style.rich = renderCtx.labelOption.rich;
if (renderCtx.additionalLabelOption) {
style = {...style, ...renderCtx.additionalLabelOption};
}
}
let borderRadius: number[];

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

@ -20,7 +20,8 @@ import {
EChartsSeriesItem,
EChartsTooltipTrigger,
EChartsTooltipWidgetSettings,
measureThresholdLabelOffset, timeAxisBandWidthCalculator
measureThresholdLabelOffset,
timeAxisBandWidthCalculator
} from '@home/components/widget/lib/chart/echarts-widget.models';
import {
autoDateFormat,
@ -30,7 +31,7 @@ import {
textStyle,
tsToFormatTimeUnit
} from '@shared/models/widget-settings.models';
import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared';
import { LabelLayoutOptionCallback, XAXisOption, YAXisOption } from 'echarts/types/dist/shared';
import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
import {
formatValue,
@ -45,7 +46,7 @@ import {
import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient';
import tinycolor from 'tinycolor2';
import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes';
import { SeriesLabelOption } from 'echarts/types/src/util/types';
import { LabelFormatterCallback, LabelLayoutOption, SeriesLabelOption } from 'echarts/types/src/util/types';
import {
BarRenderContext,
BarRenderSharedContext,
@ -58,6 +59,7 @@ import { TbColorScheme } from '@shared/models/color.models';
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
import { DatePipe } from '@angular/common';
import { BuiltinTextPosition } from 'zrender/src/core/types';
export enum TimeSeriesChartType {
default = 'default',
@ -513,6 +515,11 @@ export interface TimeSeriesChartNoAggregationBarWidthSettings {
barWidth?: TimeSeriesChartBarWidth;
}
export interface TimeSeriesChartBarWidthSettings {
barGap: number;
intervalGap: number;
}
export enum TimeSeriesChartAnimationEasing {
linear = 'linear',
quadraticIn = 'quadraticIn',
@ -568,6 +575,7 @@ export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
yAxes: TimeSeriesChartYAxes;
xAxis: TimeSeriesChartXAxisSettings;
animation: TimeSeriesChartAnimationSettings;
barWidthSettings: TimeSeriesChartBarWidthSettings;
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
}
@ -622,6 +630,10 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
animationEasingUpdate: TimeSeriesChartAnimationEasing.cubicOut,
animationDelayUpdate: 0
},
barWidthSettings: {
barGap: 0.3,
intervalGap: 0.6
},
noAggregationBarWidthSettings: {
strategy: TimeSeriesChartNoAggregationBarWidthStrategy.group,
groupWidth: {
@ -683,6 +695,7 @@ export interface LineSeriesSettings {
pointLabelPosition: SeriesLabelPosition;
pointLabelFont: Font;
pointLabelColor: string;
pointLabelFormatter?: string | LabelFormatterCallback;
pointShape: TimeSeriesChartShape;
pointSize: number;
fillAreaSettings: SeriesFillSettings;
@ -693,9 +706,12 @@ export interface BarSeriesSettings {
borderWidth: number;
borderRadius: number;
showLabel: boolean;
labelPosition: SeriesLabelPosition;
labelPosition: SeriesLabelPosition | BuiltinTextPosition;
labelFont: Font;
labelColor: string;
labelFormatter?: string | LabelFormatterCallback;
labelLayout?: LabelLayoutOption | LabelLayoutOptionCallback;
additionalLabelOption?: {[key: string]: any};
backgroundSettings: SeriesFillSettings;
}
@ -983,7 +999,7 @@ export const generateChartData = (dataItems: TimeSeriesChartDataItem[],
let series = generateChartSeries(dataItems,
stack, noAggregation, barRenderSharedContext, darkMode);
if (thresholdItems.length) {
const thresholds = generateChartThresholds(thresholdItems, darkMode);
const thresholds = generateChartThresholds(thresholdItems);
series = series.concat(thresholds);
}
return series;
@ -1017,7 +1033,7 @@ export const parseThresholdData = (value: any): TimeSeriesChartThresholdValue =>
return thresholdValue;
};
const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], darkMode: boolean): Array<LineSeriesOption> => {
const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[]): Array<LineSeriesOption> => {
const series: Array<LineSeriesOption> = [];
for (const item of thresholdItems) {
if (isDefinedAndNotNull(item.value)) {
@ -1215,7 +1231,8 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
const lineSeriesOption = seriesOption as LineSeriesOption;
lineSeriesOption.type = 'line';
lineSeriesOption.label = createSeriesLabelOption(item, lineSettings.showPointLabel,
lineSettings.pointLabelFont, lineSettings.pointLabelColor, lineSettings.pointLabelPosition, darkMode);
lineSettings.pointLabelFont, lineSettings.pointLabelColor, lineSettings.pointLabelPosition,
lineSettings.pointLabelFormatter, darkMode);
lineSeriesOption.step = lineSettings.step ? lineSettings.stepType : false;
lineSeriesOption.smooth = lineSettings.smooth;
if (lineSettings.smooth) {
@ -1256,9 +1273,11 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
}
item.barRenderContext.visualSettings = barVisualSettings;
item.barRenderContext.labelOption = createSeriesLabelOption(item, barSettings.showLabel,
barSettings.labelFont, barSettings.labelColor, barSettings.labelPosition, darkMode);
barSettings.labelFont, barSettings.labelColor, barSettings.labelPosition, barSettings.labelFormatter, darkMode);
item.barRenderContext.additionalLabelOption = barSettings.additionalLabelOption;
barSeriesOption.renderItem = (params, api) =>
renderTimeSeriesBar(params, api, item.barRenderContext);
barSeriesOption.labelLayout = barSettings.labelLayout;
}
}
seriesOption.data = item.data;
@ -1266,19 +1285,41 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
};
const createSeriesLabelOption = (item: TimeSeriesChartDataItem, show: boolean,
labelFont: Font, labelColor: string, position: SeriesLabelPosition,
labelFont: Font, labelColor: string, position: SeriesLabelPosition | BuiltinTextPosition,
labelFormatter: string | LabelFormatterCallback,
darkMode: boolean): SeriesLabelOption => {
let labelStyle: ComponentStyle = {};
if (show) {
labelStyle = createChartTextStyle(labelFont, labelColor, darkMode, 'series.label');
}
let formatter: LabelFormatterCallback;
if (labelFormatter) {
if (isFunction(labelFormatter)) {
formatter = labelFormatter as LabelFormatterCallback;
} else if (labelFormatter.length) {
const formatFunction = parseFunction(labelFormatter, ['value']);
formatter = (params): string => {
let result: string;
try {
result = formatFunction(params.value[1]);
} catch (_e) {
}
if (isUndefined(result)) {
result = formatValue(params.value[1], item.decimals, item.units, false);
}
return `{value|${result}}`;
};
} else {
formatter = (params): string => {
const value = formatValue(params.value[1], item.decimals, item.units, false);
return `{value|${value}}`;
};
}
}
return {
show,
position,
formatter: (params): string => {
const value = formatValue(params.value[1], item.decimals, item.units, false);
return `{value|${value}}`;
},
formatter,
rich: {
value: labelStyle
}

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

@ -51,7 +51,7 @@ import {
EChartsOption,
echartsTooltipFormatter,
EChartsTooltipTrigger,
getAxisExtent,
getAxisExtent, getFocusedSeriesIndex,
measureXAxisNameHeight,
measureYAxisNameWidth,
toNamedData
@ -300,6 +300,8 @@ export class TbTimeSeriesChart {
const targetBarWidth = noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group ?
noAggregationBarWidthSettings.groupWidth : noAggregationBarWidthSettings.barWidth;
this.barRenderSharedContext = {
barGap: this.settings.barWidthSettings.barGap,
intervalGap: this.settings.barWidthSettings.intervalGap,
timeInterval: this.ctx.timeWindow?.interval,
noAggregationBarWidthStrategy: noAggregationBarWidthSettings.strategy,
noAggregationWidthRelative: targetBarWidth.relative,
@ -492,8 +494,9 @@ export class TbTimeSeriesChart {
},
formatter: (params: CallbackDataParams[]) =>
this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat,
this.settings, params, 0, '', -1, this.dataItems,
this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined,
this.settings, params, 0, '',
this.settings.tooltipShowFocusedSeries ? getFocusedSeriesIndex(this.timeSeriesChart) : -1,
this.dataItems, this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined,
padding: [8, 12],
backgroundColor: this.settings.tooltipBackgroundColor,
borderWidth: 0,

Loading…
Cancel
Save