项目中需要用到甘特图组件,之前的图表一直基于 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,
new Date(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 } = args
const baseItem = {
type: 'custom',
renderItem: (params, api) => renderGanttItem(params, api),
dimensions: columns,
};
return innerRows.map(row => {
return {
...baseItem,
name: row[].name,
data: row,
};
});
}

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

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

const DIM_CATEGORY_INDEX = ; // value 中类目标识的索引
const DIM_CATEGORY_NAME_INDEX = ; // value 中对应元素类型的索引
const DIM_START_TIME_INDEX = ; // value 中开始时间的索引
const DIM_END_TIME_INDEX = ; // value 中结束时间的索引 const HEIGHT_RATIO = 0.6; // 甘特图矩形元素高度缩放比例
const CATEGORY_NAME_PADDING_WIDTH = ; // 在甘特图矩形元素上展示文字时,左右 padding 的最小长度 /**
* 计算元素位置及宽高
* 如果元素超出了当前坐标系的包围盒,则剪裁这个元素
* 如果元素完全被剪掉,会返回 undefined
*/
function clipRectByRect(params, rect) {
return echarts.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([, ])[], barMaxHeight);
const height = barHeight * HEIGHT_RATIO || baseHeight * HEIGHT_RATIO;
const width = endPoint[] - startPoint[];
const x = startPoint[];
const y = startPoint[] - height / ; // 处理类目名,用于在图形上展示
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[]]) && axis[value[]].map(item => {
item && (str += `${item}/`);
});
str = str.substr(, str.length - );
// 颜色为对象时,为渐变颜色,需要手动拼接
let mark = marker;
if (isObject(color)) {
const { colorStops = [] } = color;
const endColor = colorStops[] && colorStops[].color;
const startColor = colorStops[] && colorStops[].color;
const colorStr = `background-image: linear-gradient(90deg, ${startColor}, ${endColor});`;
mark = `
<span style="
display:inline-block;
margin-right:5px;
border-radius:10px;
width:10px;
height:10px;
${colorStr}
"></span>`;
}
// 计算时长
const startTime = moment(value[]);
const endTime = moment(value[]);
let unit = '小时';
let duration = endTime.diff(startTime, 'hours');
return `
<div>${str}</div>
<div>${mark}${name}: ${duration}${unit}</div>
<div>开始时间:${startTime.format('YYYY-MM-DD HH:mm')}</div>
<div>结束时间:${endTime.format('YYYY-MM-DD HH:mm')}</div>
`;
},
},

四、自动滚屏

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

首先需要在组件中引入 dataZoom

import 'echarts/lib/component/dataZoom';

// 配置项
const option = {
...,
dataZoom: {
type: 'slider',
id: 'insideY01',
yAxisIndex: ,
zoomLock: true,
bottom: -,
startValue: this.dataZoomStartVal,
endValue: this.dataZoomEndVal,
handleSize: ,
borderColor: 'transparent',
backgroundColor: 'transparent',
fillerColor: 'transparent',
showDetail: false,
},
{
type: 'inside',
id: 'insideY02',
yAxisIndex: ,
startValue: this.dataZoomStartVal,
endValue: this.dataZoomEndVal,
zoomOnMouseWheel: false,
moveOnMouseMove: true,
moveOnMouseWheel: true,
}
}

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

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

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

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

const Timer = null;
dataZoomAutoScoll() {
Timer = setInterval(() => {
const max = this.total - ;
if (
this.dataZoomEndVal > max ||
this.dataZoomStartVal > max - this.pageSize
) {
this.dataZoomStartVal = ;
this.dataZoomEndVal = this.pageSize - ;
} else {
this.dataZoomStartVal += ;
this.dataZoomEndVal += ;
}
echarts.dispatchAction({
type: 'dataZoom',
dataZoomIndex: ,
startValue: this.dataZoomStartVal,
endValue: this.dataZoomEndVal
});
}, );
},

基于 ECharts 封装甘特图并实现自动滚屏的更多相关文章

  1. vue可视化图表 基于Echarts封装好的v-charts简介

    **vue可视化图表 基于Echarts封装好的v-charts** 近期公司又一个新的需求,要做一个订单和销售额统计的项目,需要用到可视化图表来更直观的展示数据.首先我想到的是Echarts,众所周 ...

  2. jQuery实现 自动滚屏操作

    实现自动滚屏思路: 1.滚屏即:文本的往上移动一段距离: 2.那么我们使文本每过一段时间就往上移动一段固定距离,就可实现滚屏: 3.直到文本底部出现在浏览器窗口中,专业点就是 文本移动的距离 + 浏览 ...

  3. 帆软报表(finereport)实现自动滚屏效果

    例如Demo:IOS平台年度数据报表. 展示内容丰富,一个页面中存在多个图表.内容,超出了浏览器窗口的大小导致内容展示不全. 为了能够预览这个报表的全部内容,可以使用JS滚屏效果来实现. 操作步骤: ...

  4. FineReport中如何实现自动滚屏效果

    对于一些特殊的模板,可能为了展示的更加丰富.全面会在一个页面放置很多图表.表格等内容.由于内容过多,超出了浏览器窗口的大小导致内容展示不全的情况.这样我们就需要用到JS滚屏效果来解决,这里主要介绍在F ...

  5. echarts绘制甘特图

      在setoption之后添加这段代码: window.addEventListener('resize', function () { myChart.resize();   }); 图表就能随着 ...

  6. jquery实现自动滚屏效果,适用用公告新闻等滚屏

    从网络上找到的例子,自己做了下扩展,原示例是向上滚动,扩展了一个向下滚动的方法: <html xmlns="http://www.w3.org/1999/xhtml"> ...

  7. 基于MATLAB2016b图形化设计自动生成Verilog语言的积分模块及其应用

    在电力电子变流器设备中,常常需要计算发电量,由于电力电子变流器设备一般是高频变流设备,所以发电量的计算几乎时实时功率的积分,此时就会用到一个积分模块.发电量计算的公式如下:Q=∫P. FPGA由于其并 ...

  8. 基于Echarts的股票K线图展示

    发布时间:2018-10-31   技术:javascript+html5+canvas   概述 基于echarts的股票K线图展示,只需引用单个插件,通过简单配置,导入数据,即可实现炫酷复杂的K线 ...

  9. 关于ECharts甘特图的实现

    对于使用ECharts图表的步骤,每种图表都是一致的,相信大家也都了解 此处只分享甘特图的option,代码如下: option: { title: { text: '项目实施进度表', left: ...

随机推荐

  1. JS高阶---instanceof

    一句话: .

  2. JS高阶---函数

    [问题] [主体] 1.什么是函数? ①实现特定功能 ②多条语句的封装体 ③可以重复执行的代码块 2.为什么用函数? 提高代码的复用性,提升效率 3.如何定义函数? ①函数声明定义 ②函数表达式定义③ ...

  3. 20190922 「HZOJ NOIP2019 Round #7」20190922模拟

    综述 这次是USACO2019JAN Gold的题目. \(\mathrm{Cow Poetry}\) 题解 因为每句诗的长度一定是\(k\),所以自然而然想到背包. 设\(opt[i][j]\)代表 ...

  4. C++ vector初始化方式

    C++的初始化方法很多,各种初始化方法有一些不同. (1): vector<int> ilist1; 默认初始化,vector为空, size为0,表明容器中没有元素,而且 capacit ...

  5. 学习-velocity

    Velocity是什么?  Velocity是一个基于java的模板引擎(template engine).它允许任何人仅仅简单的使用模板语言(template language)来引用由java代码 ...

  6. MySQL实战45讲学习笔记:第十三讲

    一.引子 经常会有同学来问我,我的数据库占用空间太大,我把一个最大的表删掉了一半的数据,怎么表文件的大小还是没变? 那么今天,我就和你聊聊数据库表的空间回收,看看如何解决这个问题. 这里,我们还是针对 ...

  7. vue图片放大、缩小、旋转等

    用于图片浏览的Vue组件,支持旋转.缩放.翻转等操作,基于viewer.js. 效果: 安装 使用npm命令安装 npm install v-viewer 使用 引入v-viewer及必需的css样式 ...

  8. 【线上问题系列】DB字段类型变更导致核心服务不可用

    背景 业务说明 接到一个业务需求,往DB表中某个字段里新增一些数据,该字段本来是text类型,发现根据业务需求来说,新增数据超过text类型的最大长度,因此需要对数据库表的该字段类型做变更,变更为了M ...

  9. luogu P5606 小 K 与毕业旅行 - 构造 - 多项式

    题目传送门 传送门 先考虑 $a_i > 0$ 的情况.考虑构造这样一个顺序:$a_i$ 要么和后面的数的乘积都大于 $w$ 要么都小于等于 $w$. 这个构造可以这样做: vector< ...

  10. ORACLE--10G安装问题( error while loading shared libraries)

    01,问题描述 问题一: WARNING: directory '/u01/app/oracle/product/10.2.0' is not owned by root WARNING: direc ...