import { AxisOptions, AxisLayoutData, CustomAxisOptions, AxisSourceData, AxisData, AxisLabelOptions, DataItem } from '../../models';
import { SeriesCategory } from '../../constants';
import { DefaultOptions, AxisName } from './config';
import { GlobalData } from '@constants/globalData';
import AtomComponent from '../../atomComponent';
import { computeAxisTickData, checkIsTickFromZero } from './utils';
import { datetime, merge } from '../../utils';
export default abstract class Axis extends AtomComponent {
// 静态属性
axisName: string;
axisIndex = 0; // y轴下标
options: AxisOptions & CustomAxisOptions = {};
seriesCategory = SeriesCategory.NONE;
sourceData: AxisSourceData[];
layoutData: AxisLayoutData[] = [];
constructor(chart, axisName: AxisName, axisdata: AxisData, options: AxisOptions, axisIndex: number) {
super({ chart });
this.axisName = axisName;
this.axisIndex = axisIndex;
this.options = this.processOptions(options, DefaultOptions[axisName]);
this.sourceData = this.preProcessData(axisdata);
this.layoutData = this.processLayoutData();
this.processGlobalData();
}
/**
* 画坐标轴线
*/
protected drawLine() {}
/**
* 画坐标轴刻度
*/
protected drawTick() {}
/**
* 画坐标轴文本
*/
protected drawLabel() {}
/**
* 画坐标轴分隔线
*/
protected drawSplitLine() {}
/**
* 配置项解析
* 1.[合并]配置 2.[识别]排布方向
*/
private processOptions(userOptions, defaultOptions) {
const options = merge(defaultOptions, userOptions);
const { type } = options;
options.isVertical = (this.axisName === AxisName?.XAxis && type !== 'value') || (this.axisName === AxisName?.YAxis && type === 'value');
return options;
}
/**
* 数据标准化
*/
private preProcessData(axisdata: AxisData): AxisSourceData[] {
const { type } = this.options;
const isValueAxis = type === 'value';
// const isYAxis = this.axisName === AxisName.YAxis;
let dataSrouce: DataItem[] = null;
// valueAxis数据处理
if (isValueAxis) {
dataSrouce = axisdata?.valueAxisDatas[this.axisIndex]; // if (isYAxis)
return dataSrouce
.filter(item => !item.isFake)
.map(({ value }) => ({
value: `${value}`,
isSampled: true,
}));
}
// baseAxis数据处理
dataSrouce = axisdata?.baseAxisData;
return dataSrouce
// .filter(item => !item.isFake)
.map(({ name }) => ({
value: name,
isSampled: true,
}));
}
/**
* 数据采样
*/
private sampleData(data: AxisSourceData[]) {
const count = data.length;
let { splitNumber = count } = this.options;
if (count < splitNumber) splitNumber = count;
const interval = Math.floor(count / splitNumber);
return data.map((item, index) => {
const { value } = item;
return {
value,
isSampled: index % interval === 0, // 仅选取部分点作为样本点
};
});
}
/**
* 数据渲染布局
* @param data
* @param axisLabel
* @returns
*/
private makeLayoutData(
isValueAxis: boolean,
params?: {
axisSourceDatas: AxisSourceData[];
axisLabel: AxisLabelOptions;
},
): AxisLayoutData[] {
if (isValueAxis) {
// value轴
const isTickFromZero = checkIsTickFromZero(this.series);
const axisTickData = {
axisSourceDatas: this.sourceData,
isTickFromZero,
};
const { standardTicks } = computeAxisTickData(axisTickData);
return standardTicks.map((item: number) => ({
text: `${item}`,
rotate: 0,
isSampled: null,
}));
}
// base轴
const { axisSourceDatas, axisLabel } = params;
return axisSourceDatas.map((item: AxisSourceData) => {
const { value, isSampled = false } = item;
return ({
text: value,
rotate: axisLabel.rotate, // || autoRotate,
isSampled,
});
});
}
/*
* 坐标轴布局、抽样
* 1.type=category -分类 数量多时 排布; (cate类型无数据抽样)
* 2.type=time -针对timestamp类型处理 数据抽样算法扩展
* 3.type=value -针对横向排布图
*
* 【ext】
* boundaryGap参数:
* cate类型 true/false
* value/time类型 [left, right];
*/
private processLayoutData(): AxisLayoutData[] {
if (!this.sourceData) return;
const { type = 'category', axisLabel } = this.options;
let layoutData: AxisLayoutData[] = [];
switch (type) {
case 'value': {
// type='value':值的格式为number
layoutData = this.makeLayoutData(true);
break;
}
case 'category': {
// type='category':值的格式为string
// 抽样
this.sourceData = this.sampleData(this.sourceData);
// 布局
const params = {
axisSourceDatas: this.sourceData,
axisLabel,
};
layoutData = this.makeLayoutData(false, params);
break;
}
case 'time': {
// type='time': 值的格式为timeStamp 或 string形式的时间描述
// 抽样
this.sourceData = this.sampleData(this.sourceData);
// 刻度计算
const regularData = this.sourceData
.map((item: AxisSourceData) => {
const { value, isSampled } = item;
if (!value) return {
value: '',
isSampled,
};
if (!datetime.isValid(value)) {
console.warn(`时间字符串【${value}】格式异常`);
return {
value: '',
isSampled,
};
}
const labelFormatter = axisLabel?.formatter as string;
return {
value: datetime.format(value, labelFormatter),
isSampled,
};
});
// 布局
const params = {
axisSourceDatas: regularData,
axisLabel,
};
layoutData = this.makeLayoutData(false, params);
break;
}
case 'log':
default:
break;
}
return layoutData;
}
/**
* xAxis全局数据处理
*/
private processGlobalData() {
const {
chart,
canvas: { size },
options: { isVertical, type },
} = this;
// 1.向组织&传递数据
const isNeedMarkLineData = this.axisName === AxisName.XAxis && isVertical;
if (isNeedMarkLineData) {
const tickGap = size.width / (this.options?.boundaryGap ? this.sourceData.length : this.sourceData.length - 1);
const axisSourceData = this.sourceData.map((item, i) => {
const { value, isSampled }: AxisSourceData = item;
return {
x: i * tickGap,
value,
isSampled,
};
});
const axisGlobalData = {
axisSourceData,
axisType: this.options.type,
};
chart.setGlobalData(GlobalData.BaseAxis4MarkLine, axisGlobalData);
}
// 2.向、、...
const isNeedBaseAxisData = type === 'value';
if (isNeedBaseAxisData) {
const isTickFromZero = checkIsTickFromZero(this.series);
const axisTickData = {
axisSourceDatas: this.sourceData,
isTickFromZero,
};
const { tickMin, tickMax } = computeAxisTickData(axisTickData);
const curValueAxisData = {
axisIndex: this.axisIndex,
axis: {
max: tickMax,
min: tickMin,
},
data: this.sourceData.map(item => item.value || 0),
};
const preValueAxisData = chart.getGlobalData(GlobalData.ValueAxis);
const valueAxisData = !preValueAxisData ? [curValueAxisData] : [...preValueAxisData, curValueAxisData];
chart.setGlobalData(GlobalData.ValueAxis, valueAxisData);
}
}
}