詳解gantt甘特圖可拖拽、編輯(vue、react都可用 highcharts)
前言
Excel功能強大,應用廣泛。隨著web應用的興起和完善,用戶的要求也越來越高。很多Excel的功能都搬到瞭sass裡面。恨不得給他們做個Excel出來。。。程序員太難瞭。。。
去年我遇到瞭一個甘特圖的需求,做瞭很多工作,也寫瞭兩篇博客。一篇是用 GSTC 這個包做的甘特圖,另一篇是自己手寫瞭一個簡易的甘特圖。兩個的效果都不理想,特別是GSTC,問題很多,好多道友看瞭博客遇到瞭問題,慚愧,沒能幫大傢解決這個問題。之前太忙瞭,這個甘特圖就再沒搞,直到今天發現瞭新的包,幾乎是完全符合我們的需求。
首先,我們用的是 highcharts;其次,大團隊的產品,後期維護有保障,文檔也齊全。
我用 Vue3 寫的,但是highcharts不區分他,是js包,所以無論 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 的文件是未壓縮混淆的,註釋也很詳細。這是我合並文件時的版本Highcharts Gantt JS v9.3.1 (2021-11-05),這個也是當前的穩定版本。
功能有點簡單,好像代碼沒什麼好說的。關鍵的地方我都做瞭註釋,還有不明白的可以留言或者評論。
最後,還存在一個問題,我沒仔細研究源碼,這個示例還存在一個問題,就是拖拽事件沒有中斷,而且直接修改瞭圖表的數據展示。比如,縱向拖動換行時,左側表格的數據會變化。暫時我還沒有找到滿意解決的方法。目前,我在拖拽結束的回調 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)的文章就介紹到這瞭,更多相關vue 甘特圖gantt內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Django顯示可視化圖表的實踐
- 基於Day.js更優雅的處理JavaScript中的日期
- Vue如何使用Dayjs計算常用日期詳解
- JavaScript圖表插件highcharts詳解
- 交互式可視化js庫gojs使用介紹及技巧