基於 ECharts 封裝甘特圖並實現自動滾屏

項目中須要用到甘特圖組件,以前的圖表一直基於 EChart 開發,但 EChart 自己沒有甘特圖組件,須要自行封裝html

通過一番鏖戰,終於完成了...api

我在工程中參考 v-chart 封裝了一套圖表組件,因此這裏只介紹甘特圖組件的實現,圖表的初始化、數據更新、自適應等不在這裏介紹數組

 

1、約定數據格式數據結構

EChart 自己沒有甘特圖,但能夠經過 EChart 提供的「自定義」方法 type: 'custom' 開發echarts

const option = { series: [{ type: 'custom', renderItem: (params, api) => { // do sth  }, data, }] }

這裏的 data 就是數據集,它是一個二維數組,主要須要兩個參數:ide

name: 名稱,能夠在 legend 和 tooltip 中展現函數

value:參數集合,自定義的圖表時須要的參數均可以放到這個數組裏工具

若是須要其它的配置,也能夠按照 ECharts 的 series 結構添加別的字段this

我自定義的數據結構是這樣的:spa

{ 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 的索引去取對應的參數

 

2、處理數據 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[0].name, data: row, }; }); }

當 type 指定爲 'custom' 的時候,series 的元素能夠添加 dimensions 字段,用來定義每一個維度的信息

處理數據的核心是 renderItem 方法,該方法提供了 paramsapi 兩個參數,最後須要返回對應的圖形元素信息

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) { 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([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 提供的座標系信息,截取出元素的圖形信息

 

3、自定義 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 = ` <span style="  display:inline-block; margin-right:5px; border-radius:10px; width:10px; height:10px; ${colorStr} "></span>`;  } // 計算時長 const startTime = moment(value[3]); const endTime = moment(value[4]); 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> `; }, },

 

4、自動滾屏

若是甘特圖的數據過多,堆在一屏展現就會顯得很窄,這時候能夠結合 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); },
相關文章
相關標籤/搜索