gantt甘特图可拖拽、编辑(vue、react都可用 highcharts)
前言
Excel功能强大,应用广泛。随着web应用的兴起和完善,用户的要求也越来越高。很多Excel的功能都搬到了sass里面。恨不得给他们做个Excel出来。。。程序员太难了。。。
去年我遇到了一个甘特图的需求,做了很多工作,也写了两篇博客。一篇是用 GSTC 这个包做的甘特图,另一篇是自己手写了一个简易的甘特图。两个的效果都不理想,特别是GSTC,问题很多,好多道友看了博客遇到了问题,惭愧,没能帮大家解决这个问题。之前太忙了,这个甘特图就再没搞,知道今天发现了新的包,几乎是完全符合我们的需求。
首先,我们用的是 highcharts;其次,大团队后期维护有保障,文档也齐全。
我用 Vue 写的,但是highcharts不区分,所以无论 vue react 还是原生多页面都没问题。
接下来先看一下我们的需求,也是最基本的,需要实现的功能,然后会有效果图的gif,最后就是源代码,我放在了Git上,觉得好用,麻烦给了star。
需求
1、横轴左侧是表格数据,可以展示基本信息
2、横轴右侧是时间轴,可以切换不同精度的时间展示
3、横向数据块有多个,最好可以叠加
4、数据块可以拖拽、点击等,修改任务的时间和其他信息
效果图
这个highcharts,不仅实现了左边表格,右边图标,而且数据是联动的;右边横轴是时间轴,可以自定义格式;数据允许叠加,不冲突;数据有点击等各种事件,可以选中编辑单个数据块;数据可以拖拽,比如上下换列拖拽、水平拖拽,还可以单边拖拽,而且事件都有回调函数。这些功能基本可以满足我们的需求。比如对时间段、时间长度、数据信息的修改和展示。
源码地址、代码解析
先贴一下我代码的Git地址,点击 GitHub源代码 下载源代码。建议直接下源码,跑项目,另外,这个项目是vue3的,不过对于这种包,写法差别不大。
我贴一下代码,对功能实现做一个讲解,当然注释写的也是很详细的。
首先,highcharts-gantt.js 是专门用来实现甘特图的文件,draggable-points.js 是实现点数据绑定的文件。因为vue直接引入有找不到变量的问题,我将draggable-points的两个module直接添加到了highcharts-gantt里面,然后重新压缩,没有混淆,所以最终的包只有160K+,小了很多,大家可以直接用。压缩之后的源代码放心使用就行,我只是合并了2个文件的功能函数,但是也要格外提醒,不是官方的源文件了,感兴趣的同学可以去看官方源代码, .src 的文件是未压缩混淆的,注释也很详细。
功能有点简单,好像代码没什么好说的。关键的地方我都做了注释,还有不明白的可以留言或者评论。
最后,还存在一个问题,我没仔细研究源码,这个示例还存在一个问题,就是拖拽事件没有中断,而且直接修改了图表的数据展示。比如,纵向拖动换行时,左侧表格的数据会变化。暂时我还没有找到满意解决的方法。目前,我在拖拽结束的回调 drop 回调中,对数据做了处理,然后将我们希望的数据回写,重新渲染图表,同样你也可以做不能拖拽或者时间冲突等各种校验,达到上面我所说的需求。但是还有一点瑕疵,就是拖拽过程中的数据变化,左侧表格的数据我不希望他变化,暂时没能解决掉。如果您有好的案例、好的使用、好的建议,都希望可以提出来,共同进步。
<div class="hightChart-gantt">
<div id="container"></div>
<button @click="getData">打印当前数据</button>
</div>
</template> <script>
import { defineComponent, onMounted, ref } from 'vue';
import * as Highcharts from '@jsModule/highcharts/highcharts-gantt.src.js'
import dayjs from 'dayjs'
import{ WEEKS } from './constants' // api文档: https://api.Highcharts.com.cn/gantt/index.html
// 社区地址: https://forum.jianshukeji.com/tags/c/Highcharts/35/Highcharts-gantt
// 官方示例: https://www.highcharts.com.cn/demo/gantt/interactive-gantt // 待解决问题
// 1、拖拽中断: 用户操作应该需要校验,但是现在对中断用户操作这块还没搞明白。
// 解决方案: 目前的做法是,在 drop 里面做判断,根据业务逻辑,做出提示,重新渲染数据。能实现,不够友好。 export default defineComponent({
name: 'hightCharts-gantt',
components: {},
setup () {
const gantt = ref({});
// 官方建议用UTC的时间,鉴于业务需要,我们需要和数据库时间保持统一,得看数据库的存储格式
const data = [
{start: '2021-6-1 0',end: '2021-6-1 18',factory: '华为',material: 'P50', uid: 1, y: 0, completed: 0.35},
{start: '2021-6-2 8',end: '2021-6-2 16',factory: '华为',material: 'P50', uid: 2, y: 0},
{start: '2021-6-3 8',end: '2021-6-4 24',factory: '华为',material: 'P50', uid: 3, y: 0},
{start: '2021-6-4 12',end: '2021-6-5 15',factory: '华为',material: 'P50', uid: 4, y: 0}, {start: '2021-6-1 8',end: '2021-6-1 12',factory: '小米',material: '红米3', uid: 5, y: 1},
{start: '2021-6-3 3',end: '2021-6-3 9',factory: '小米',material: '红米3', uid: 6, y: 1}, {start: '2021-6-1 6',end: '2021-6-1 16',factory: '苹果',material: 'iPhone13', uid: 7, y: 2},
{start: '2021-6-2 3',end: '2021-6-2 19',factory: '苹果',material: 'iPhone13', uid: 8, y: 2},
{start: '2021-6-3 8',end: '2021-6-3 17',factory: '苹果',material: 'iPhone13', uid: 9, y: 2}, {start: '2021-6-1 12',end: '2021-6-1 24',factory: 'OPPO',material: 'Reno7', uid: 10, y: 3},
{start: '2021-6-2 5',end: '2021-6-2 18',factory: 'OPPO',material: 'Reno7', uid: 11, y: 3},
{start: '2021-6-3 1',end: '2021-6-5 12',factory: 'OPPO',material: 'Reno7', uid: 12, y: 3},
];
let newData = data.map(item => {
item.start = dayjs(item.start).valueOf();
item.end = dayjs(item.end).valueOf();
return item
}); // 全局配置,需要在图标初始化之前配置
Highcharts.setOptions({
global: {
useUTC: false // 不使用utc时间
},
// 默认都是英文的,这里做了部分中文翻译
lang: {
noData: '暂无数据',
weekdays: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
months: ['一月', '儿月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
},
});
const dragStart = (e) => {
}
const drag = (e) => {
}
const drop = (e) => {
const { newPoint = {}, target = {} } = e;
if(newPoint.y || newPoint.y === 0) {
let list = [], tar = newData.find(item => item.y === newPoint.y && item.uid !== target.uid);
list = newData.map(item => {
// 当前拖拽数据
if(item.uid === target.uid) {
return {
...item,
factory: tar.factory,
material: tar.material,
...newPoint
}
} else {
return item
}
})
gantt.value.update({
series: [{
data: list
}]
})
}
}
// 选中,可以弹窗,编辑一些业务数据
const handleSelect = (e) => {
console.log('选中')
}
// 获取最终数据
const getData = () => {
let data = gantt.value.series[0].data.map(item => {
return {
uid: item.uid,
factory: item.factory,
material: item.material,
start: item.start,
end: item.end
}
})
console.log(data)
} onMounted(() => {
try {
gantt.value = Highcharts.ganttChart('container', {
title: {
text: 'hightCharts甘特图示例'
},
xAxis: [{
currentDateIndicator: true,
tickPixelInterval: 70,
grid: {
borderWidth: 1, // 右侧表头边框宽度
cellHeight: 35, // 右侧日期表头高度
},
labels: {
align: 'center',
formatter: function() {
return `${dayjs(this.value).format('M月D')} ${WEEKS[dayjs(this.value).day()]}`;
}
},
}, {
labels: {
align: 'center',
formatter: function() {
return `${dayjs(this.value).format('YYYY年M月')}`;
}
},
}],
yAxis: {
type: 'category',
grid: {
enabled: true,
borderColor: 'rgba(0,0,0,0.3)',
borderWidth: 1,
columns: [
{ title: { text: '工厂' }, labels: { format: '{point.factory}' } },
{ title: { text: '型号' }, labels: { format: '{point.material}' } },
]
}
},
tooltip: {
formatter: function () {
return `<div>
工厂: ${this.point.factory}<br/>
开始时间: ${dayjs(this.point.start).format('YYYY-MM-DD HH:mm:ss')}<br/>
结束时间: ${dayjs(this.point.end).format('YYYY-MM-DD HH:mm:ss')}<br/>
</div>`
}
},
series: [{ data: newData }],
plotOptions: {
series: {
animation: false, // Do not animate dependency connectors
dragDrop: {
draggableX: true, // 横向拖拽
draggableY: true, // 纵向拖拽
dragMinY: 0, // 纵向拖拽下限
dragMaxY: 3, // 纵向拖拽上限
dragPrecisionX: 3600000 // 横向拖拽精度,单位毫秒
},
dataLabels: {
enabled: true,
format: '{point.factory}-{point.uid}',
style: {
cursor: 'default',
pointerEvents: 'none'
}
},
allowPointSelect: true,
point: {
events: {
dragStart: dragStart,
drag: drag,
drop: drop,
select: handleSelect
}
}
}
},
exporting: {
sourceWidth: 1000
},
credits: { // 去掉右下角版权信息
enabled: false
},
});
} catch (error) {
console.log(error)
}
}) return {
gantt,
getData
}
},
})
</script> <style scoped>
.hightChart-gantt {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
#container {
max-width: 1200px;
min-width: 800px;
height: 400px;
margin: 1em auto;
}
</style>
gantt甘特图可拖拽、编辑(vue、react都可用 highcharts)的更多相关文章
- 强大的拖拽组件:React DnD 的使用
强大的拖拽组件:React DnD 的使用 react.js 10.6k 次阅读 · 读完需要 25 分钟 17 文章首发我的个人blog : 原文链接 学习 React DnD 的最初原因是阅读 ...
- 【 D3.js 进阶系列 — 6.2 】 饼状图的拖拽
本文讲解稍微复杂一些的拖拽应用,即拖拽饼图的各部分. 在[入门 - 第 9.1 章]讲解了如何制作饼状图.饼状图的各部分是用具有宽度的弧线来表示的.在与用户进行交互的时候,如果每一部分都能拖拽,是很有 ...
- 原生js拖拽、jQuery拖拽、vue自定义指令拖拽
原生js拖拽: <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...
- D3.js 力导向图的拖拽(drag)与缩放(zoom)
不知道大家会不会跟我一样遇到这样的问题,在之前做的力导向图的基础上加上缩放功能的时候,拖动节点时整体会平移不再是之前酷炫的效果(失去了拉扯的感觉!).天啊,简直不能接受如此丑X的效果.经过不懈的努力终 ...
- Vue 可拖拽组件 Vue Smooth DnD 详解和应用演示
本文发布自 https://www.cnblogs.com/wenruo/p/15061907.html 转载请注明出处. 简介和 Demo 展示 最近需要有个拖拽列表的需求,发现一个简单好用的 Vu ...
- gantt甘特图的制作过程
甘特图主要是用来做项目管理的,可以清楚的看到任务间的逻辑关系,任务与时间关系和任务间并行关系. 在甘特图中,横轴方向表示时间,纵轴方向并列着活动列表.图表内可以用线条.数字.文字代号等来表示计划(实际 ...
- 移动端的拖拽排序在react中实现 了解一下
最近做一个拖拽排序的功能找了好几个有一个步骤简单,结合redux最好不过了,话不多说上代码 第一步: npm install react-draggable-tags --save 第二步 sort. ...
- 使用GridVIew显示Gantt(甘特图),动态增减列
说明:本例是做了工厂的排机报表 一.根据查询日期初始化GridView列 private void IniGridView(DateTime p_DateS,DateTime p_DateE) { / ...
- 卡片拖拽(vue拖拽事件)
<template> <div class="wrapper wrapper-content" id="main" v-cloak> & ...
随机推荐
- Python | 一键生成九宫格图片
一键生成九宫格图片 首先我们准备几张图片: 将代码文件放在放置图片的地方,用软件打开: 点击运行,在当前目录下会生成一个文件夹: 打开新生成的文件夹: 打开对应图片的名称文件夹: 如果不想图片被分成9 ...
- 洛谷2494 [SDOI2011]保密 (分数规划+最小割)
自闭一早上 分数规划竟然还能被卡精度 首先假设我们已经知道了到每个出入口的时间(代价) 那我们应该怎么算最小的和呢? 一个比较巧妙的想法是,由于题目规定的是二分图. 我们不妨通过最小割的形式. 表示这 ...
- 一个神秘的oj2093 花园的守护之神(最小割)
给定一张无向图,你每次可以将一条路的权值增加1,询问最少增加多少次才会使得\(s->t\)的最短路改变 QwQ一看到这个题,我就用种最小割的感觉 我们可以把最短路上的点取出来,然后做最小割呀!! ...
- SpringBoot入门08-整合Mabatis
整合所需的依赖 注解方式和映射文件方式的mybatis都可以被整合进springboot 创建springboot的web项目后,在pom加入spring-mybatis和mysql-jdbc和thy ...
- GoLang设计模式11 - 备忘录模式
备忘录模式是一种行为型设计模式.这种模式允许我们保存对象在某些关键节点时的必要信息,以便于在适当的时候可以将之恢复到之前的状态.通常它可以用来帮助设计撤销/恢复操作. 下面是备忘录设计模式的主要角色: ...
- iOS平台 | 快速集成华为AGC认证服务
介绍 如何让用户根据已有的账号来进行登录注册呢?在应用中集成华为AGC认证服务SDK来轻松快速地实现这个功能. 本篇内容根据官网文档指导集成过程总结完成,关于集成步骤,官网的资料写的有点多,现在我总结 ...
- DDD领域驱动设计-概述-Ⅰ
如果我看得更远,那是因为我站在巨人的肩膀上.(If I have seen further it is by standing on ye shoulder of Giants.) ...
- Vue项目搭建常用的配置文件,request.js和vue.config.js
request.js用来请求数据,封装的代码如下: import axios from 'axios' const request = axios.create({ timeout: 5000 }) ...
- 2021.10.26考试总结[冲刺NOIP模拟16]
T1 树上的数 \(DFS\)一遍.结构体存边好像更快? \(code:\) T1 #include<bits/stdc++.h> using namespace std; namespa ...
- 转:VIVADO使用技巧:设置DCI与内部参考电压
本文转自:Vivado使用技巧(12):设置DCI与内部参考电压 - 灰信网(软件开发博客聚合) (freesion.com) DCI与内部参考电压 Xilinx FPGA提供了DCI(Digital ...