UI: Improve time series charts ticks generation.

This commit is contained in:
Igor Kulikov 2024-03-14 12:32:51 +02:00
parent 15a88a90a3
commit c4bc1ba6bb
4 changed files with 196 additions and 93 deletions

View File

@ -207,3 +207,144 @@ index b8a9b95..8e4cb2f 100644
return;
}
var axisValueLabel = axisPointerViewHelper.getValueLabel(axisValue, axisModel.axis, ecModel, axisItem.seriesDataIndices, axisItem.valueLabelOpt);
diff --git a/node_modules/echarts/lib/coord/axisHelper.js b/node_modules/echarts/lib/coord/axisHelper.js
index a76c66b..be22cb0 100644
--- a/node_modules/echarts/lib/coord/axisHelper.js
+++ b/node_modules/echarts/lib/coord/axisHelper.js
@@ -187,7 +187,9 @@ export function createScaleByModel(model, axisType) {
});
default:
// case 'value'/'interval', 'log', or others.
- return new (Scale.getClass(axisType) || IntervalScale)();
+ return new (Scale.getClass(axisType) || IntervalScale)({
+ ticksGenerator: model.get('ticksGenerator')
+ });
}
}
}
diff --git a/node_modules/echarts/lib/coord/cartesian/Grid.js b/node_modules/echarts/lib/coord/cartesian/Grid.js
index 5b18f02..4960e67 100644
--- a/node_modules/echarts/lib/coord/cartesian/Grid.js
+++ b/node_modules/echarts/lib/coord/cartesian/Grid.js
@@ -91,11 +91,11 @@ var Grid = /** @class */function () {
var scale = axis.scale;
if (
// Only value and log axis without interval support alignTicks.
- isIntervalOrLogScale(scale) && model.get('alignTicks') && model.get('interval') == null) {
+ isIntervalOrLogScale(scale) && model.get('alignTicks') && model.get('interval') == null && model.get('ticksGenerator') == null) {
axisNeedsAlign.push(axis);
} else {
niceScaleExtent(scale, model);
- if (isIntervalOrLogScale(scale)) {
+ if (isIntervalOrLogScale(scale) && !scale.isBlank()) {
// Can only align to interval or log axis.
alignTo = axis;
}
@@ -105,10 +105,15 @@ var Grid = /** @class */function () {
// All axes has set alignTicks. Pick the first one.
// PENDING. Should we find the axis that both set interval, min, max and align to this one?
if (axisNeedsAlign.length) {
- if (!alignTo) {
- alignTo = axisNeedsAlign.pop();
- niceScaleExtent(alignTo.scale, alignTo.model);
+ while (!alignTo && axisNeedsAlign.length) {
+ var axis = axisNeedsAlign.pop();
+ niceScaleExtent(axis.scale, axis.model);
+ if (!axis.scale.isBlank()) {
+ alignTo = axis;
+ }
}
+ }
+ if (axisNeedsAlign.length && alignTo) {
each(axisNeedsAlign, function (axis) {
alignScaleTicks(axis.scale, axis.model, alignTo.scale);
});
diff --git a/node_modules/echarts/lib/scale/Interval.js b/node_modules/echarts/lib/scale/Interval.js
index 1094662..8f4e07a 100644
--- a/node_modules/echarts/lib/scale/Interval.js
+++ b/node_modules/echarts/lib/scale/Interval.js
@@ -46,12 +46,17 @@ import * as numberUtil from '../util/number.js';
import * as formatUtil from '../util/format.js';
import Scale from './Scale.js';
import * as helper from './helper.js';
+import { isFunction } from 'zrender/lib/core/util.js';
var roundNumber = numberUtil.round;
var IntervalScale = /** @class */function (_super) {
__extends(IntervalScale, _super);
- function IntervalScale() {
- var _this = _super !== null && _super.apply(this, arguments) || this;
+ function IntervalScale(setting) {
+ var _this = _super.call(this, setting) || this;
_this.type = 'interval';
+ var ticksGenerator = _this.getSetting('ticksGenerator');
+ if (isFunction(ticksGenerator)) {
+ _this._ticksGenerator = ticksGenerator;
+ }
// Step is calculated in adjustExtent.
_this._interval = 0;
_this._intervalPrecision = 2;
@@ -104,7 +109,17 @@ var IntervalScale = /** @class */function (_super) {
var extent = this._extent;
var niceTickExtent = this._niceExtent;
var intervalPrecision = this._intervalPrecision;
- var ticks = [];
+ var ticksGenerator = this._ticksGenerator;
+ var ticks;
+ if (ticksGenerator) {
+ try {
+ ticks = ticksGenerator(extent, interval, niceTickExtent, intervalPrecision);
+ if (ticks) {
+ return ticks;
+ }
+ } catch (_e) {}
+ }
+ ticks = [];
// If interval is 0, return [];
if (!interval) {
return ticks;
diff --git a/node_modules/echarts/types/dist/shared.d.ts b/node_modules/echarts/types/dist/shared.d.ts
index ca74097..98f8b18 100644
--- a/node_modules/echarts/types/dist/shared.d.ts
+++ b/node_modules/echarts/types/dist/shared.d.ts
@@ -2422,6 +2422,9 @@ interface AxisBaseOptionCommon extends ComponentOption, AnimationOptionMixin {
max: number;
}) => ScaleDataValue);
}
+
+declare type NumericAxisTicksGenerator = (extent?: number[], interval?: number, niceTickExtent?: number[], intervalPrecision?: number) => {value: number}[];
+
interface NumericAxisBaseOptionCommon extends AxisBaseOptionCommon {
boundaryGap?: [number | string, number | string];
/**
@@ -2447,6 +2450,8 @@ interface NumericAxisBaseOptionCommon extends AxisBaseOptionCommon {
* Will be ignored if interval is set.
*/
alignTicks?: boolean;
+
+ ticksGenerator?: NumericAxisTicksGenerator;
}
interface CategoryAxisBaseOption extends AxisBaseOptionCommon {
type?: 'category';
diff --git a/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts b/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts
index c5c2792..d524b70 100644
--- a/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts
+++ b/node_modules/echarts/types/src/coord/axisCommonTypes.d.ts
@@ -56,6 +56,9 @@ export interface AxisBaseOptionCommon extends ComponentOption, AnimationOptionMi
max: number;
}) => ScaleDataValue);
}
+
+export declare type NumericAxisTicksGenerator = (extent?: number[], interval?: number, niceTickExtent?: number[], intervalPrecision?: number) => {value: number}[];
+
export interface NumericAxisBaseOptionCommon extends AxisBaseOptionCommon {
boundaryGap?: [number | string, number | string];
/**
@@ -81,6 +84,8 @@ export interface NumericAxisBaseOptionCommon extends AxisBaseOptionCommon {
* Will be ignored if interval is set.
*/
alignTicks?: boolean;
+
+ ticksGenerator?: NumericAxisTicksGenerator;
}
export interface CategoryAxisBaseOption extends AxisBaseOptionCommon {
type?: 'category';

View File

@ -44,7 +44,6 @@ import {
getDataKey,
getLatestSingleTsValue,
overlayStyle,
simpleDateFormat,
textStyle
} from '@shared/models/widget-settings.models';
import { DataKey } from '@shared/models/widget.models';
@ -55,10 +54,9 @@ import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser';
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
import {
TimeSeriesChartAxisSettings,
TimeSeriesChartKeySettings,
TimeSeriesChartSeriesType,
TimeSeriesChartSettings, TimeSeriesChartYAxisSettings
TimeSeriesChartSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { DeepPartial } from '@shared/models/common';
@ -189,8 +187,7 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
showSplitLines: true,
min: 'dataMin',
max: 'dataMax',
intervalCalculator:
'var scale = axis.scale; return !scale.isBlank() ? ((scale.getExtent()[1] - scale.getExtent()[0]) / 2) : undefined;'
ticksGenerator: (extent?: number[]) => (extent ? [{ value: (extent[0] + extent[1]) / 2}] : [])
}
},
tooltipDateInterval: false,

View File

@ -19,21 +19,23 @@ import {
EChartsOption,
EChartsSeriesItem,
EChartsTooltipTrigger,
EChartsTooltipWidgetSettings, getYAxis,
EChartsTooltipWidgetSettings,
measureThresholdLabelOffset
} from '@home/components/widget/lib/chart/echarts-widget.models';
import {
autoDateFormat, AutoDateFormatSettings,
autoDateFormat,
AutoDateFormatSettings,
ComponentStyle,
Font,
simpleDateFormat,
textStyle, tsToFormatTimeUnit
textStyle,
tsToFormatTimeUnit
} from '@shared/models/widget-settings.models';
import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared';
import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
import {
formatValue,
isDefinedAndNotNull,
isFunction,
isNumeric,
isUndefined,
isUndefinedOrNull,
@ -42,7 +44,6 @@ import {
} from '@core/utils';
import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient';
import tinycolor from 'tinycolor2';
import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D';
import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes';
import { SeriesLabelOption } from 'echarts/types/src/util/types';
import {
@ -320,6 +321,12 @@ export const defaultXAxisTicksFormat: AutoDateFormatSettings = {
export type TimeSeriesChartYAxisId = 'default' | string;
export type TimeSeriesChartTicksGenerator =
(extent?: number[], interval?: number, niceTickExtent?: number[], intervalPrecision?: number) => {value: number}[];
export type TimeSeriesChartTicksFormatter =
(value: any) => string;
export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSettings {
id?: TimeSeriesChartYAxisId;
order?: number;
@ -329,8 +336,8 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting
splitNumber?: number;
min?: number | string;
max?: number | string;
intervalCalculator?: string;
ticksFormatter?: string;
ticksGenerator?: TimeSeriesChartTicksGenerator | string;
ticksFormatter?: TimeSeriesChartTicksFormatter | string;
}
export const timeSeriesChartYAxisValid = (axis: TimeSeriesChartYAxisSettings): boolean =>
@ -788,8 +795,6 @@ export interface TimeSeriesChartYAxis {
decimals: number;
settings: TimeSeriesChartYAxisSettings;
option: YAXisOption & ValueAxisBaseOption;
intervalCalculator?: (axis: Axis2D) => number;
ticksFormatter?: (value: any) => string;
}
export const createTimeSeriesYAxis = (units: string,
@ -800,11 +805,37 @@ export const createTimeSeriesYAxis = (units: string,
settings.tickLabelColor, darkMode, 'axis.tickLabel');
const yAxisNameStyle = createChartTextStyle(settings.labelFont,
settings.labelColor, darkMode, 'axis.label');
let ticksFormatter: (value: any) => string;
if (settings.ticksFormatter && settings.ticksFormatter.length) {
ticksFormatter = parseFunction(settings.ticksFormatter, ['value']);
let ticksFormatter: TimeSeriesChartTicksFormatter;
if (settings.ticksFormatter) {
if (isFunction(settings.ticksFormatter)) {
ticksFormatter = settings.ticksFormatter as TimeSeriesChartTicksFormatter;
} else if (settings.ticksFormatter.length) {
ticksFormatter = parseFunction(settings.ticksFormatter, ['value']);
}
}
const yAxis: TimeSeriesChartYAxis = {
let ticksGenerator: TimeSeriesChartTicksGenerator;
let minInterval: number;
let interval: number;
let splitNumber: number;
if (settings.ticksGenerator) {
if (isFunction(settings.ticksGenerator)) {
ticksGenerator = settings.ticksGenerator as TimeSeriesChartTicksGenerator;
} else if (settings.ticksGenerator.length) {
ticksGenerator = parseFunction(settings.ticksGenerator, ['extent', 'interval', 'niceTickExtent', 'intervalPrecision']);
}
}
if (!ticksGenerator) {
interval = settings.interval;
if (isUndefinedOrNull(interval)) {
if (isDefinedAndNotNull(settings.splitNumber)) {
splitNumber = settings.splitNumber;
} else {
minInterval = (1 / Math.pow(10, decimals));
}
}
}
return {
id: settings.id,
decimals,
settings,
@ -818,6 +849,10 @@ export const createTimeSeriesYAxis = (units: string,
scale: true,
min: settings.min,
max: settings.max,
minInterval,
splitNumber,
interval,
ticksGenerator,
name: settings.label,
nameLocation: 'middle',
nameRotate: settings.position === AxisPosition.left ? 90 : -90,
@ -853,7 +888,8 @@ export const createTimeSeriesYAxis = (units: string,
if (ticksFormatter) {
try {
result = ticksFormatter(value);
} catch (_e) {}
} catch (_e) {
}
}
if (isUndefined(result)) {
result = formatValue(value, decimals, units, false);
@ -869,54 +905,6 @@ export const createTimeSeriesYAxis = (units: string,
}
}
};
if (settings.intervalCalculator && settings.intervalCalculator.length) {
yAxis.intervalCalculator = parseFunction(settings.intervalCalculator, ['axis']);
}
return yAxis;
};
export const updateYAxisIntervals = (chart: ECharts,
yAxis: TimeSeriesChartYAxis, empty: boolean): boolean => {
let changed = false;
let interval: number;
let splitNumber: number;
let minInterval: number;
if (!empty) {
interval = calculateYAxisInterval(chart, yAxis);
if (isUndefinedOrNull(interval)) {
if (isDefinedAndNotNull(yAxis.settings.splitNumber)) {
splitNumber = yAxis.settings.splitNumber;
} else {
minInterval = (1 / Math.pow(10, yAxis.decimals));
}
}
}
if (yAxis.option.interval !== interval) {
yAxis.option.interval = interval;
changed = true;
}
if (yAxis.option.splitNumber !== splitNumber) {
yAxis.option.splitNumber = splitNumber;
changed = true;
}
if (yAxis.option.minInterval !== minInterval) {
yAxis.option.minInterval = minInterval;
changed = true;
}
return changed;
};
const calculateYAxisInterval = (chart: ECharts, yAxis: TimeSeriesChartYAxis): number | undefined => {
let interval = yAxis.settings.interval;
if (yAxis.intervalCalculator) {
const axis = getYAxis(chart, yAxis.id);
if (axis) {
try {
interval = yAxis.intervalCalculator(axis);
} catch (_e) {}
}
}
return interval;
};
export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettings,

View File

@ -27,7 +27,8 @@ import {
TimeSeriesChartDataItem,
timeSeriesChartDefaultSettings,
timeSeriesChartKeyDefaultSettings,
TimeSeriesChartKeySettings, TimeSeriesChartNoAggregationBarWidthStrategy,
TimeSeriesChartKeySettings,
TimeSeriesChartNoAggregationBarWidthStrategy,
TimeSeriesChartSeriesType,
TimeSeriesChartSettings,
TimeSeriesChartShape,
@ -39,7 +40,7 @@ import {
TimeSeriesChartYAxis,
TimeSeriesChartYAxisId,
TimeSeriesChartYAxisSettings,
updateDarkMode, updateYAxisIntervals
updateDarkMode
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { ResizeObserver } from '@juggle/resize-observer';
import {
@ -611,11 +612,6 @@ export class TbTimeSeriesChart {
this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis', 'xAxis', 'grid'], lazyUpdate: true});
}
changed = this.updateYAxesIntervals(this.yAxisList);
if (changed) {
this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis'], lazyUpdate: true});
}
if (this.yAxisList.length) {
const extent = getAxisExtent(this.timeSeriesChart, this.yAxisList[0].id);
const min = extent[0];
@ -680,9 +676,6 @@ export class TbTimeSeriesChart {
yAxis.option.scale = scaleYAxis;
changed = true;
}
if (updateYAxisIntervals(this.timeSeriesChart, yAxis, this.yAxisEmpty(yAxis))) {
changed = true;
}
}
return changed;
}
@ -693,22 +686,6 @@ export class TbTimeSeriesChart {
return !axisBarDataItems.length;
}
private yAxisEmpty(yAxis: TimeSeriesChartYAxis): boolean {
const axisDataItems = this.dataItems.filter(d => d.yAxisId === yAxis.id && d.enabled &&
d.data.length);
return !axisDataItems.length;
}
private updateYAxesIntervals(axisList: TimeSeriesChartYAxis[]): boolean {
let changed = false;
for (const yAxis of axisList) {
if (updateYAxisIntervals(this.timeSeriesChart, yAxis, this.yAxisEmpty(yAxis))) {
changed = true;
}
}
return changed;
}
private minTopOffset(): number {
const showTickLabels =
!!this.yAxisList.find(yAxis => yAxis.settings.show && yAxis.settings.showTickLabels);