echarts 自定义甘特图
效果
echarts 官网实例
思路
官网展示的甘特图,其实也是通过自定义type: 'custom'
实现的。
1.grid,xAxis,yAxis
属性可以根据你的业务需求进行样式调整。
2.tooltip
,是可选的主要控制的是甘特图鼠标hover的提示文字。
3.series
是核心代码。
实现
核心代码如下
series: [
{
id: 'flightData',
type: 'custom',
renderItem: (params:any, api:any)=>{
// seriesData 是每个甘特图段的数据值
console.log(seriesData,'seriesData')
let color = api.value(DIM_TIME_TASK) === currentTask ? currentColor : 'grey'
// color 是根据我的业务需求进行的,单行甘特段颜色设置
var categoryIndex = api.value(DIM_CATEGORY_INDEX);
var timeArrival = api.coord([api.value(DIM_TIME_ARRIVAL), categoryIndex]); //单个段落起始值
var timeDeparture = api.coord([api.value(DIM_TIME_DEPARTURE), categoryIndex]);//单个段落结束值
var barLength = timeDeparture[0] - timeArrival[0] || 50;
var barHeight = api.size([0, 1])[1] * HEIGHT_RATIO;
var x = timeArrival[0];
var y = timeArrival[1] - barHeight;
var rectNormal = this.clipRectByRect(params, {
x: x,
y: y,
width: barLength,
height: barHeight,
});
console.log(rectNormal,'rectNormal')
return {
type: 'group',
children: [
{
type: 'rect',
shape: rectNormal,
style: api.style({
fill: color,
textFill: '#fff',
text:aliasValue ? api.value(DIM_TIME_AlIAS) + '' : api.value(DIM_TIME_TASK) + ''
})
},
]
};
},
encode: {
x: [DIM_TIME_ARRIVAL, DIM_TIME_DEPARTURE],
y: DIM_CATEGORY_INDEX,
tooltip: [aliasValue ? DIM_TIME_AlIAS : DIM_TIME_TASK, DIM_TIME_ARRIVAL , DIM_TIME_DEPARTURE] //此处控制tooltip展示的值
},
data:seriesData
},
{
type: 'custom',
renderItem: (params:any,api:any)=>{
var interval = api.size([0,1])[1]
var newY = api.coord([0, api.value(0)])[1];
if (newY < params.coordSys.y + 5) {
return;
}
return {
type: 'group',
position: [60, newY - interval/2 ],
children: [
{
type: 'text',
style: {
text:arrDataY[api.value(0)],
textAlign: 'center',
textFill: colorY,
}
}
]
};
},
encode: {
x: -1,
y: 0
},
data: arrDataY.map((it:any,i:number)=>i)
}
]
先来看ganttItem的实现
请仔细看seriseData的值,会发现下标为0,1名字都是demo,最后一项都是0,6,7都是demo14,最后一项都是5,那是因为是我将后台返回的数据进行了进行分组(分组方法下面会讲到)。
同时会按照分组后,为其添加下标,官网数据也是如此的,。
也就是说,如果想让甘特图一行显示多块儿,就要满足这两个数组会有同样的值(注意:这个值必须是数字,在canvas绘制并且会根据你的这个值进行排序绘画),这也就是为什么我的demo会显示在最下面第一行。
细心的同学也就会发现renderItem
其实是循环处理的seriesData,如上图我打印的4,0,1分别就对应了排序下标,开始时间,结束时间。
简而言之:就是将data数据组成 [ [单个甘特段开始值,单个甘特段结束值,当前甘特行在哪行显示的下标] ],其余的都可以不要
数组对象分组处理
export const getSeriesData = (arr:Array<any>,taskVal:string)=>{
// arr 原始数据 taskVal 根据哪个字段进行分组 我是根据 task进行的分组
let arrData:any = [];
console.log(arr,'原始数据 两笔数据')
// 第一步 query 数据整合
arr.forEach((item:any)=>{
let fieldList:any = []
item.fields.forEach((it:any) => {
fieldList.push({
name:it.name,
value:it.values.buffer
})
});
const fieldData = getFieldData(fieldList)
arrData = arrData.concat(fieldData.newArr);
})
console.log(arrData,'将两笔数据进行整合')
// 第二部 根据task进行分组处理
var taskRather:any = []
var taskArr:any = []
for(let i = 0 ; i < arrData.length ; i ++){
const arrDataInfo = arrData[i];
if(!taskRather.find((it:string)=>it === arrDataInfo[taskVal])){
// 不重复task
taskArr.push({
task:arrDataInfo[taskVal],
children:[arrDataInfo]
})
taskRather.push(arrDataInfo[taskVal])
}else{
// 重复的task
for(let j = 0 ; j < taskArr.length ; j ++){
const taskInfo = taskArr[j];
if(arrDataInfo[taskVal] === taskInfo.task){
taskInfo.children.push(arrDataInfo)
}
}
}
}
console.log(taskArr,'分组后的数据')
// 第三步生成对应的index,用于在panel显示 tips:必须判断children长度,进行newData添加
let newData:any = [ ]
for(let i = 0 ; i < taskArr.length ; i ++ ){
const taskInfo = taskArr[i];
if(taskInfo.children.length > 1){
for(let j = 0 ; j < taskInfo.children.length; j ++){
const data = taskInfo.children[j]
data.group = i
newData.push(Object.values(data))
}
}else{
for(let j = 0 ; j < taskInfo.children.length; j ++){
const data = taskInfo.children[j]
data.group = i
newData.push(Object.values(data))
}
}
}
console.log(newData,'最后组成的符合echarts的数据')
return {
arrData:newData,
}
}
export const getFieldData = (arr:Array<any>)=>{
let newArr:any = [];
for(let i = 0 ; i < arr[0].value.length ; i ++){
let obj:any = {}
for(let j = 0 ; j < arr.length ; j ++){
obj[arr[j].name] = arr[j].value[i]
}
newArr.push(obj)
}
return {newArr}
}
运行结果如下
y轴自定义的实现就相对简单些
arrDataY 就对应了每行的名字,data取下标就变成了[0,1,2,3,4,5]。
有个注意点是position: [60, newY - interval/2 ],
我对interval/2这样y轴的文字就会显示在每行的中间位置上,不/2就会在每行x轴显示。
yAxis的max属性 换成rrDataY.length 即可
yAxis: {
axisTick: { show: false },
splitLine: { show: false},
axisLine: { show: true },
axisLabel: { show: true },
min: 0,
max:arrDataY.length
},
总结
其实甘特图整个不是特别复杂,只需要组成echarts符合的数据就好,官网的功能更复杂所以看起来很复杂。
为方便理解:可以将下面testData进行替换GanttItem中的data值,就会很明显的理解了
const testData = [
[
'0',
1496840400000, //2017-6-7 21:0:0
1496934000000, //2017-6-8 23:0:0
],[
'1',
1496924700000, //2017-6-8 20:25:0
1496934000000, //2017-6-8 23:0:0
],[
'1',
1496840400000, //2017-6-7 21:0:0
1496878800000, //2017-6-8 7:40:0
],[
'2',
1496916600000, //2017-6-8 18:10:0
1496934000000,//2017-6-8 23:0:0
]
]