前文概述
日常需求需要一个可以展示项目集合总时间流程,以及每个环节占的时间的总览。
选型
基于项目开发是vue2.x所以倾向找原本就是vue所写的甘特图插件。github 里vue gantt排名前三。
插件 | gantt-schedule-timeline-calendar | gantt-elastic | vue-gantt-chart |
---|---|---|---|
优点 | 1. 节点交互充分,能满足所有操作。 2. 样式优雅。 3. 表格slot | 1. 节点有事件吐出 2. 数据配置简单 | 1. 虚拟滚动一定不会卡 2. 支持自定义描述和容器块 |
缺点 | 要钱,不开源 | 不维护,前面插件的前身。条类型固定样式固定无法调节 | 整体样式怪异,左侧配置需要自己传component去渲染,没有父子关系的数据概念 |
综合而言,上述的gantt-elastic比较符合,只要把Chart row进行改造即可使用。
关键代码分析
插件关键步骤图
// 折叠操作
makeTaskTree(task, tasks) {
for (let i = 0, len = tasks.length; i < len; i++) {
let current = tasks[i];
if (current.parentId === task.id) {
if (task.parents.length) {
task.parents.forEach((parent) => current.parents.push(parent));
}
if (!Object.prototype.propertyIsEnumerable.call(task, '__root')) {
current.parents.push(task.id);
current.parent = task.id;
} else {
current.parents = [];
current.parent = null;
}
current = this.makeTaskTree(current, tasks); // 递归找完一个点的下游的父节点
task.allChildren.push(current.id);
task.children.push(current.id);
current.allChildren.forEach((childId) => task.allChildren.push(childId));
}
}
return task;
}
因为形成树结构,后续折叠操作只需要修改tasks里的状态key,全局监听tasks即可完成filter 重新画。
// 滚动同步,chart监听wheel事件 通过refs获取每个模块修改scrollLeft,scrollTop属性。
onWheelChart(ev) {
// if (!ev.shiftKey && ev.deltaX === 0) {
let top = this.state.options.scroll.top + ev.deltaY;
const chartClientHeight = this.state.options.rowsHeight;
const scrollHeight = this.state.refs.chartGraph.scrollHeight - chartClientHeight;
if (top < 0) {
top = 0;
} else if (top > scrollHeight) {
top = scrollHeight;
}
this.scrollTo(null, top);
let left = this.state.options.scroll.left + ev.deltaX;
const chartClientWidth = this.state.refs.chartScrollContainerHorizontal.clientWidth;
const scrollWidth = this.state.refs.chartScrollContainerHorizontal.scrollWidth - chartClientWidth;
if (left < 0) {
left = 0;
} else if (left > scrollWidth) {
left = scrollWidth;
}
this.scrollTo(left);
},
// row components group类型 子模块全集合一起渲染
foundChildrens() {
const childrens = this.task.allChildren.map((id) => {
return this.root.getTask(id);
});
this.task.group = childrens;
}
// 算row展示长度是否可以放下text情况。
calText(task) {
let marginLeft = this.root.style['chart-row-inline-text']['marginLeft'];
let text = task.text;
let textWidth = this.root.state.ctx.measureText(text).width + marginLeft;
if (textWidth < task.width) {
return text;
} else {
let ellipsisWidth = this.root.state.ctx.measureText('...').width;
const textArr = text.split('');
let currentWidth = ellipsisWidth;
const res = [];
for (let text of textArr) {
const textWidth = this.root.state.ctx.measureText(text).width;
if (textWidth + currentWidth > task.width) {
break;
} else {
currentWidth += textWidth;
res.push(text);
}
}
return res.length ? res.join('') + '...' : '';
}
}
最终我们发现,图是否能成功画完的关键在于最小粒度的宽高(row,column)明确,所有的计算都基于此。gantt-elastic 通过provide自身实例在每一块都可以记录下对应的宽高共享使用。
后续改造
- 修改为virtual scroll,避免渲染压力。只需要记录滚动距离。通过Math.floor(top/taskHeight)即可算出当前startIdx。 然后修改 visibleTasks 即可重画。
觉得有用的话点个赞吧,或者有更好的方案也可以在评论区留言,谢谢各位。