echart 时间滚动_基于 ECharts 封装甘特图并实现自动滚屏

项目中需要用到甘特图组件,之前的图表一直基于 EChart 开发,但 EChart 本身没有甘特图组件,需要自行封装

经过一番鏖战,终于完成了...

我在工程中参考 v-chart 封装了一套图表组件,所以这里只介绍甘特图组件的实现,图表的初始化、数据更新、自适应等不在这里介绍

一、约定数据格式

EChart 本身没有甘特图,但可以通过 EChart 提供的“自定义”方法 type: 'custom' 开发

const option ={

series: [{

type:'custom',

renderItem: (params, api) =>{//do sth

},

data,

}]

}

这里的 data 就是数据集,它是一个二维数组,主要需要两个参数:

name:名称,可以在 legend 和 tooltip 中展示

value:参数集合,自定义的图表时需要的参数都可以放到这个数组里

如果需要其它的配置,也可以按照 ECharts 的 series 结构添加别的字段

我自定义的数据结构是这样的:

{

name,

itemStyle: {

normal: {

color: color||defaultColor,

},

},//value 为约定写法,依序为“类目对应的索引”、“状态类型”、“状态名称”、“开始时间”、“结束时间”

value: [

index,

type,

name,newDate(start).getTime(),

new Date(end ||Date.now()).getTime(),

],

}

注意:series.data 中的元素需要根据状态划分,不能根据类目(Y轴)划分,这样才能保证图例 legend 的正常显示

最终的 data 结构如图:

自定义的核心是 renderItem 函数,这个函数的本质就是:将 data 中的参数 value 处理之后,映射到对应的坐标轴上,具体处理参数的逻辑完全自定义

甘特图就需要计算出各个数据块的高度和宽度,然后映射到对应的类目轴(Y轴)和时间轴(X轴)上

由于甘特图会用到时间轴(X轴),所以定义的 value 中需要开始时间和结束时间的时间戳

为了区分该数据属于类目轴(Y轴)的哪一条类目,还需要对应类目的索引 index

如果还有其它的需要,比如自定义 tooltip,还可以在 value 中添加其它的参数

但一定要约定好参数的顺序,因为 renderItem 函数是根据 value 的索引去取对应的参数

二、处理数据 Series

//处理数据

function getGantSeries(args) {const { innerRows, columns } =argsconst baseItem ={

type:'custom',

renderItem: (params, api) => renderGanttItem(params, api),

dimensions: columns,

};return innerRows.map(row =>{return{

...baseItem,

name: row[0].name,

data: row,

};

});

}

当 type 指定为 'custom' 的时候,series 的元素可以添加 dimensions 字段,用来定义每个维度的信息

处理数据的核心是 renderItem 方法,该方法提供了 params 和 api 两个参数,最后需要返回对应的图形元素信息

const DIM_CATEGORY_INDEX = 0; //value 中类目标识的索引

const DIM_CATEGORY_NAME_INDEX = 1; //value 中对应元素类型的索引

const DIM_START_TIME_INDEX = 3; //value 中开始时间的索引

const DIM_END_TIME_INDEX = 4; //value 中结束时间的索引

const HEIGHT_RATIO = 0.6; //甘特图矩形元素高度缩放比例

const CATEGORY_NAME_PADDING_WIDTH = 20; //在甘特图矩形元素上展示文字时,左右 padding 的最小长度

/**

* 计算元素位置及宽高

* 如果元素超出了当前坐标系的包围盒,则剪裁这个元素

* 如果元素完全被剪掉,会返回 undefined*/function clipRectByRect(params, rect) {returnecharts.graphic.clipRectByRect(rect, {

x:params.coordSys.x,

y:params.coordSys.y,

width:params.coordSys.width,

height:params.coordSys.height,

});

}//渲染甘特图元素

function renderGanttItem(params, api, extra) {const { isShowText, barMaxHeight, barHeight } =extra;//使用 api.value(index) 取出当前 dataItem 的维度

const categoryIndex =api.value(DIM_CATEGORY_INDEX);//使用 api.coord(...) 将数值在当前坐标系中转换成为屏幕上的点的像素值

const startPoint =api.coord([api.value(DIM_START_TIME_INDEX), categoryIndex]);const endPoint =api.coord([api.value(DIM_END_TIME_INDEX), categoryIndex]);//使用 api.size(...) 取得坐标系上一段数值范围对应的长度

const baseHeight = Math.min(api.size([0, 1])[1], barMaxHeight);const height = barHeight * HEIGHT_RATIO || baseHeight *HEIGHT_RATIO;const width = endPoint[0] - startPoint[0];const x = startPoint[0];const y = startPoint[1] - height / 2;//处理类目名,用于在图形上展示

const categoryName = api.value(DIM_CATEGORY_NAME_INDEX) + '';const categoryNameWidth =echarts.format.getTextRect(categoryName).width;const text = width > categoryNameWidth + CATEGORY_NAME_PADDING_WIDTH ? categoryName : '';const rectNormal = clipRectByRect(params, { x, y, width, height });const rectText = clipRectByRect(params, { x, y, width, height });return{

type:'group',

children: [

{//图形元素形状: 'rect', circle', 'sector', 'polygon'

type: 'rect',

ignore:!rectNormal, //是否忽略(忽略即不渲染)

shape: rectNormal,//映射 option 中 itemStyle 样式

style: api.style(),

},

{//在图形上展示类目名

type: 'rect',

ignore:!isShowText || !rectText,

shape: rectText,

style: api.style({

fill:'transparent',

stroke:'transparent',

text: text,

textFill:'#fff',

}),

},

],

};

}

上面是我用的 renderItem 方法全貌,主要是使用 api 提供的工具函数计算出元素的视觉宽高

再使用 echarts 提供的 graphic.clipRectByRect 方法,结合参数 params 提供的坐标系信息,截取出元素的图形信息

三、自定义 tooltip

如果数据格式正确,到这里已经能渲染出甘特图了,但一个图表还需要其它的细节,比如 tooltip 的自定义

在 renderItem 中有一个字段 encode 可以用来自定义 tooltip,但只能定义展示的文字

具体的 tooltip 排版和图例颜色(特别是渐变色)无法通过 encode 实现自定义,最终还是得通过 formatter 函数

formatter: params =>{const { value = [], marker, name, color } = params;const axis = this.columns; //类目轴(Y轴)数据//删除空标题

let str = '';

isArray(axis[value[0]]) && axis[value[0]].map(item =>{

item&& (str += `${item}/`);

});

str= str.substr(0, str.length - 1);//颜色为对象时,为渐变颜色,需要手动拼接

let mark =marker;if(isObject(color)) {const { colorStops = [] } =color;const endColor = colorStops[0] && colorStops[0].color;const startColor = colorStops[1] && colorStops[1].color;const colorStr = `background-image: linear-gradient(90deg, ${startColor}, ${endColor});`;

mark=``;

}//计算时长

const startTime = moment(value[3]);const endTime = moment(value[4]);

let unit= '小时';

let duration= endTime.diff(startTime, 'hours');return`

${str}
${mark}${name}: ${duration}${unit}
开始时间:${startTime.format('YYYY-MM-DD HH:mm')}
结束时间:${endTime.format('YYYY-MM-DD HH:mm')}
`;

},

},

四、自动滚屏

如果甘特图的数据过多,堆在一屏展示就会显得很窄,这时候可以结合 dataZoom 实现滚屏

首先需要在组件中引入 dataZoom

import 'echarts/lib/component/dataZoom';//配置项

const option ={

...,

dataZoom: {

type:'slider',

id:'insideY01',

yAxisIndex:0,

zoomLock:true,

bottom:-10,

startValue:this.dataZoomStartVal,

endValue:this.dataZoomEndVal,

handleSize:0,

borderColor:'transparent',

backgroundColor:'transparent',

fillerColor:'transparent',

showDetail:false,

},

{

type:'inside',

id:'insideY02',

yAxisIndex:0,

startValue:this.dataZoomStartVal,

endValue:this.dataZoomEndVal,

zoomOnMouseWheel:false,

moveOnMouseMove:true,

moveOnMouseWheel:true,

}

}

然后需要设定甘特图每一行的高度 barHeight,同时获取甘特图组件的高度

通过这两个高度计算出每屏可以展示的甘特图数据的数量 pageSize

const GANT_ITEM_HEIGHT = 56;const height = this.$refs.chartGantRef.$el.clientHeight;this.pageSize = Math.floor(height /GANT_ITEM_HEIGHT);//设置 dataZoom 的起点

this.dataZoomStartVal = 0;this.dataZoomEndVal = this.pageSize - 1;

然后通过定时器派发事件,修改 dataZoom 的 startValue 和 endValue,实现自动滚屏的效果

const Timer = null;

dataZoomAutoScoll() {

Timer= setInterval(() =>{const max = this.total - 1;if(this.dataZoomEndVal > max ||

this.dataZoomStartVal > max - this.pageSize

) {this.dataZoomStartVal = 0;this.dataZoomEndVal = this.pageSize - 1;

}else{this.dataZoomStartVal += 1;this.dataZoomEndVal += 1;

}

echarts.dispatchAction({

type:'dataZoom',

dataZoomIndex:0,

startValue:this.dataZoomStartVal,

endValue:this.dataZoomEndVal

});

},2000);

},

你可能感兴趣的:(echart,时间滚动)