基本的实现思想,见https://segmentfault.com/a/11...,时隔接近一年,新项目中,对于多层x轴的实现进行了分装。
多层不等分x轴的实现逻辑是在呈现原始图形的下方,继续画出多个grid,每一层x轴对应一个grid,故该封装旨在返回这些多出的grid、xAxis、yAxis、seires、以及原始图形要使用的xAxisData。
export const buildMultiXExtends: ({
points,
xNames,
width,
dataSeriesMaxGridIndex,
marginLeft,
marginRight
}: {
points: EchartsPoint<any>[];
xNames: string[];
width: number;
dataSeriesMaxGridIndex: number;
marginLeft: number;
marginRight: number;
}) => {
xAxisData: string[];
xAxisExtends: XAXisComponentOption[];
yAxisExtends: YAXisComponentOption[];
gridExtends: GridComponentOption[];
seriesextends: SeriesOption[];
} = (params) => {
const { points, xNames, width, dataSeriesMaxGridIndex, marginLeft, marginRight } = params;
const xNum = points.length;
const xlevel: number = xNames.length;
// 设置x轴的数据
const xAxisData: string[] = [
// {value:星期1}
];
// 设置多层的x轴,配置series,grid,xAxis,yAxis
const xAxisExtends: XAXisComponentOption[] = [];
const yAxisExtends: YAXisComponentOption[] = [];
const gridExtends: GridComponentOption[] = [];
const seriesextends: SeriesOption[] = [];
// 存放所有层维度数据的数组
const xAxisArray: { [key: string]: number }[][] = [];
// 先按维度的层次拼接x的名字
const tempxaxisname: string[][] = [];
for (let i: number = 0; i < xNum; i++) {
const tempxaxislevelname: string[] = [];
tempxaxislevelname.push(points[i].x[0]);
for (let j: number = 1; j < xlevel; j++) {
tempxaxislevelname.push(tempxaxislevelname[j - 1] + ',' + points[i].x[j]);
}
tempxaxisname.push(tempxaxislevelname);
}
// console.log(tempxaxisname, '按层次拼接好的x的名字---------------');
// 设置x轴的数据
for (let i: number = 0; i < xNum; i++) {
xAxisData.push(tempxaxisname[i][xlevel - 1]);
}
// 分维度取出x轴的名字数组
for (let i: number = 0; i < xlevel; i++) {
const tempxxaisvalue: { [key: string]: number }[] = [];
// 该层循环确定一个维度上的名称和所占的单元格的长度
// 记录当前数据是该层第几块
let group: number = 1;
for (let j = 0; j < xNum; j++) {
if (
tempxxaisvalue.length > 0 &&
Object.keys(tempxxaisvalue[tempxxaisvalue.length - 1])[0].slice(
0,
Object.keys(tempxxaisvalue[tempxxaisvalue.length - 1])[0].indexOf('%^&group')
) === tempxaxisname[j][i].substring(tempxaxisname[j][i].lastIndexOf(',') + 1)
) {
//如果和最后一位重复,则合并单元格,长度+1
// console.log("重复,需要合并");
const lastkey: string = Object.keys(tempxxaisvalue[tempxxaisvalue.length - 1])[0] + '';
const lastvalue: number = Object.values(tempxxaisvalue[tempxxaisvalue.length - 1])[0] as number;
// console.log(lastkey,'lastkey');
// console.log(lastvalue,'lastvalue');
// console.log(tempxxaisvalue[tempxxaisvalue.length-1],'tempxxaisvalue[tempxxaisvalue.length-1][0]');
tempxxaisvalue[tempxxaisvalue.length - 1] = Object.assign({}, tempxxaisvalue[tempxxaisvalue.length - 1], {
[lastkey]: lastvalue + 1
});
} else {
// console.log("不重复,不需要合并");
tempxxaisvalue.push({
[tempxaxisname[j][i].substring(tempxaxisname[j][i].lastIndexOf(',') + 1) + '%^&group' + group]: 1
});
group++;
}
// console.log(tempxxaisvalue,'tempxxaisvalue___________--');
}
xAxisArray.push(tempxxaisvalue);
}
// 外层循环走完所有的维度都已经拼成了一个对象数组,对象里面分别包裹着每一层维度的名称和对应的长度,一个对象就是一个维度
// console.log(xAxisArray, '要给多层x轴进行渲染的xAxisArray对象数据----------------');
// xAxisArray.reverse();
for (let i: number = 0; i < xlevel; i++) {
gridExtends.push({
height: 20,
bottom: (xlevel - i) * 20 + dataSeriesMaxGridIndex * 20,
left: marginLeft,
right: marginRight,
tooltip: {
// show: true,
show: false
}
});
xAxisExtends.push({
type: 'category',
gridIndex: i + 1 + dataSeriesMaxGridIndex,
axisLine: { show: false },
axisLabel: { show: false },
axisTick: { show: false }
});
yAxisExtends.push({
type: 'value',
gridIndex: i + 1 + dataSeriesMaxGridIndex,
axisLine: { show: false },
axisLabel: { show: false },
axisTick: { show: false }
});
// 当前该维度的名字字符串和对应的所占单元格长度的字符串
// console.log(xAxisArray);
// 当该层的小柱子的个数小于1000的时候,才进行数据的填入,否则返回一个大的整块的柱子
if (xAxisArray[i].length < 1000) {
// 依次填入该维度的所有出现的名称与匹配的所占单元格的长度
for (let j: number = 0; j < xAxisArray[i].length; j++) {
// 设置填充进bar中的name 的内容和样式,当长度大于当前bar的宽度时,以省略号显示
// 未处理的原始的拼接后带标志的名字字符串
const originEverytempXAxisName: string = Object.keys(xAxisArray[i][j])[0];
// console.log(origintempXAxisName,'origintempXAxisName');
//每个多层x轴的小柱子的长度
const originEverytempXAxisValue: number = Object.values(xAxisArray[i][j])[0] as number;
let tempXAxisname: string = '';
// tempsingleXAxisName[j].substring(tempsingleXAxisName[j].lastIndexOf(',')+1)为要展示的全部的字符串,末尾带有 %^&group
let everysingleXAxisName = originEverytempXAxisName.substring(originEverytempXAxisName.lastIndexOf(',') + 1);
// everysingleXAxisName为处理好的要显示的最简单的对应的名字的全部字符串
everysingleXAxisName = everysingleXAxisName.slice(0, everysingleXAxisName.indexOf('%^&group'));
if (everysingleXAxisName.length > (originEverytempXAxisValue / xNum) * 60 * (width / 800) - 1)
tempXAxisname =
everysingleXAxisName.substring(0, Math.floor((originEverytempXAxisValue / xNum) * 60 * (width / 800))) +
'..';
else tempXAxisname = everysingleXAxisName;
// console.log('处理完成一个label的显示');
seriesextends.push(
buildMultiXSeriesExtendSingle({
barWidth: originEverytempXAxisValue / xNum,
dataName: tempXAxisname,
gridIndex: i + 1 + dataSeriesMaxGridIndex,
tooltipFormatter: everysingleXAxisName
})
);
// console.log('完成一个seris的设置');
}
} else {
seriesextends.push(
buildMultiXSeriesExtendSingle({
barWidth: 1,
dataName: `!Zoom in to display ${xNames[i]}`,
gridIndex: i + 1 + dataSeriesMaxGridIndex,
tooltipFormatter: `${xNames[i]}`
})
);
}
// console.log(seriesextends, 'seriesextends+++++++++++');
}
return {
xAxisExtends,
yAxisExtends,
xAxisData,
seriesextends,
gridExtends
};
};
对于入参的几个参数,进行简单的解释:
xNames:多层x轴每一层x轴的名称,若有三层,名字分别为 年份,月份,日,该值即为['year','month','date']
width:echarts图表的宽度,计算每个小柱子是否省略显示对应名字所需
dataSeriesMaxGridIndex:画原始图形的区域的grid的Index的最大值。例子,若只有一个grid,girdIndex为0,此参数传0,若原始有3个grid区域,gridIndex最大为2,此参数传2
marginLeft, marginRight:图形区域离整个echarts的canvas左右的间距,与原始的grid的left,top设置为一样的值即可。(原始grid一定要设置left与right,且不可使用containLabel:tru属性!!!!否则对不齐多层x轴的grid与原始图形的grid)
points:点的数据,此用的结构为项目中定义好的数据结构
export interface EchartsPoint<T> {
x: string[];
y: T;
}
但在多层x轴的代码段中,只用到了字段x,此时的x的数组的长度一定是与xNames的长度一样,且位置互相对应。继续刚刚的例子,
若xNames:['year','month','date'],
points的mock数据可以是
points: [
{
x: ['2021', '10', '2'],
y: {}
},
{
x: ['2022', '10', '2'],
y: {}
},
{
x: ['2022', '10', '2'],
y: {}
},
{
x: ['2022', '10', '3'],
y: {}
},
{
x: ['2022', '11', '2'],
y: {}
},
{
x: ['2022', '11', '3'],
y: {}
},
{
x: ['2022', '11', '3'],
y: {}
}
];
PS:此处结构可以简化,项目中统一定义了如此结构,故直接复用了points的结构,其实只需要点的x的数据既可以了
方法中还使用了另一个封装的方法buildMultiXSeriesExtendSingle
// 生成多层x轴多的单个的扩展series数据的函数
export const buildMultiXSeriesExtendSingle: ({
barWidth,
dataName,
gridIndex,
tooltipFormatter
}: {
barWidth: number;
dataName: string;
gridIndex: number;
tooltipFormatter: string;
}) => SeriesOption = (params) => {
const { barWidth, dataName, tooltipFormatter, gridIndex } = params;
return {
type: 'bar',
large: true,
barWidth: `${barWidth * 100}%`,
data: [{ name: dataName, value: 1 }],
barGap: 0,
label: {
show: true,
position: 'inside',
formatter: '{b}'
},
itemStyle: {
color: '#fff',
borderColor: 'lightgray',
borderWidth: 1
},
emphasis: {
disabled: true
},
animation: false,
tooltip: {
formatter: tooltipFormatter,
show: true
},
xAxisIndex: gridIndex,
yAxisIndex: gridIndex
};
};
该方法的返回值,如何在实际场景中使用,即将返回的grid、xAxis、yAxis、series拼接在options的这些字段的后面,返回的xAxisData设置为原始的x轴的data即可
const multiXExtends = buildMultiXExtends({
points,
xNames,
width,
dataSeriesMaxGridIndex: 0,
marginLeft: 80,
marginRight: 25
});
// 设置x轴的数据
const xAxisData: string[] = multiXExtends.xAxisData;
// 设置多层的x轴,配置series,grid,xAxis,yAxis
const xAxisExtends: XAXisComponentOption[] = multiXExtends.xAxisExtends;
const yAxisExtends: YAXisComponentOption[] = multiXExtends.yAxisExtends;
const gridExtends: GridComponentOption[] = multiXExtends.gridExtends;
const seriesextends: SeriesOption[] = multiXExtends.seriesextends;
const yAxis_grid0: YAXisComponentOption = {
type: 'value',
scale: true,
gridIndex: 0,
axisLine: {
onZero: false
},
name: yAxisName,
nameGap: 65,
nameLocation: 'middle',
nameTextStyle: {
fontWeight: 'bold'
},
};
const xAxis_grid0: XAXisComponentOption = {
type: 'category',
data: xAxisData,
position: 'bottom',
gridIndex: 0,
name: xAxisName,
nameLocation: 'middle',
nameGap: xlevel * 20 + 5,
nameTextStyle: {
fontWeight: 'bold'
},
axisTick: { show: false },
axisLine: { show: false },
axisLabel: { show: false }
};
const grid_grid0 = {
top: 60,
bottom: (xlevel + 1) * 20,
left: '80px',
right: '25px'
}
const option: EChartsOption = {
// title: {
// text: titleinfo,
// },
tooltip: {
trigger: 'item',
confine: true,
show: true
},
grid: [grid_grid0, ...gridExtends],
legend: {
type: 'scroll',
orient: 'horizontal',
width: '60%',
height: '10px',
top: '5px',
left: '10px',
tooltip: {
show: true,
extraCssText: 'white-space:pre-wrap'
},
data: yNames
},
xAxis: [
xAxis_grid0,
// 下面都是多的x轴
...xAxisExtends
],
yAxis: [
yAxis_grid0,
// 下面都是多的y轴
...yAxisExtends
],
series: [...series, ...seriesextends]
};
总结:传入对应的结构匹配的入参之后,将返回的返回结构,拼入options的对应的结构上。
注意点:
①入参中的marginLeft和marginRight 需要和options的原始grid的left和right相同
②入参中的ponits的结构,在不改变封装内部代码的情况下,要与定义的结构相同,最简形式:
export interface EchartsPoint<T> {
x: string[];
}
③points下x数组与xNames的数组的值要一一对应(没有采用KeyValue的形式,省传输时的空间)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。