import * as echarts from 'echarts' function getStartTime(timestamp) { return new Date(new Date(timestamp).toLocaleDateString()).getTime(); } function renderItem(params, api) { // 1. 校验value数据是否有效 const yIndex = api.value(0); // y轴索引 const startTime = api.value(1); const endTime = api.value(2); if (isNaN(startTime) || isNaN(endTime) || startTime >= endTime) { return null; } // 2. 转换坐标(x轴是time类型,需确保转换正确) const startPoint = api.coord([startTime, yIndex]); const endPoint = api.coord([endTime, yIndex]); if (!startPoint || !endPoint) return null; // 3. 计算矩形高度(加大高度,避免高度为0) const cellHeight = api.size([0, 1])[1] * 0.8; // 从0.5改为0.8,提高可视性 const rectX = startPoint[0]; const rectY = startPoint[1] - cellHeight / 2; const rectWidth = endPoint[0] - startPoint[0]; // 4. 避免宽度为负/0 if (rectWidth <= 0) return null; // 5. 裁剪矩形(简化裁剪逻辑,避免过度裁剪) const rectShape = echarts.graphic.clipRectByRect( { x: rectX, y: rectY, width: rectWidth, height: cellHeight, }, params.coordSys // 直接用坐标系范围,简化裁剪 ); return rectShape ? { type: 'rect', transition: ['shape'], shape: rectShape, style: api.style(), } : null; } // unused function getXaxisRange(startTime) { return Array(24) .fill(startTime) .map((item, index) => { return new Date(item + index * 3600 * 1000) .toLocaleTimeString() .split(':') .slice(0, 2) .join(':'); }); } function getTodayStart(today) { const [y, m, d] = [ today.getFullYear(), today.getMonth(), today.getDate(), ]; // debugger; return new Date(y, m, d).getTime(); } /** 颜色配置 */ const types = [ { name: '运行', color: '#288AFF' }, { name: '计划停机', color: '#FFDC94' }, { name: '故障', color: '#FC9C91' }, { name: '空白', color: '#F2F4F9' }, ]; export default class GanttGraph { // tooltip - 基本是固定的 tooltip = { trigger: 'item', axisPointer: { type: 'none', }, formatter: (params) => { // debugger; const date1 = new Date(params.value[1]); const date2 = new Date(params.value[2]); return `
${date1.getMonth() + 1}-${date1.getDate()} ${String(date1.getHours()).padStart(2, '0')}:${String(date1.getMinutes()).padStart(2, '0')} ~ ${date2.getMonth() + 1}-${date2.getDate()} ${String(date2.getHours()).padStart(2, '0')}:${String(date2.getMinutes()).padStart(2, '0')}
${params.data.showName}
${params.name}
` } } grid = [] xAxis = [] yAxis = [] series = [] constructor(el, startTime) { this.el = el; this.startTime = new Date(startTime); } // 构造一个新的 grid makeGrid() { return { top: 5, right: 20, left: 100, bottom: 50, } } // 构造一个 xAxis // 新增:从数据中提取时间范围的方法 getXaxisRangeFromData(data) { let allTimes = []; data.forEach(eqArr => { eqArr.forEach(item => { if (item.startTime) { allTimes.push(item.startTime); allTimes.push(item.startTime + (item.duration || 0) * 60 * 1000); } }); }); if (allTimes.length === 0) { const today = new Date(); return [getTodayStart(today), getTodayStart(today) + 24 * 3600 * 1000]; } const minTime = Math.min(...allTimes); const maxTime = Math.max(...allTimes); // 扩展1小时的缓冲 return [minTime - 3600 * 1000, maxTime + 3600 * 1000]; } // 修改makeXaxis方法,支持传入数据动态计算范围 makeXaxis(data = []) { let [minTime, maxTime] = this.getXaxisRangeFromData(data); return [ { axisTick: { alignWithLabel: true, inside: true, }, type: 'time', min: minTime, max: maxTime, splitNumber: 24, axisLabel: { margin: 12, formatter: function (val) { return new Date(val).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); }, }, axisLine: { lineStyle: { color: '#0005', }, }, boundaryGap: false, }, ]; } // 构造一个 yAxis makeYaxis(equipmentName) { return [ // 主y轴 { data: equipmentName, type: 'category' } ] } // 构造一个 series makeSeries(xdata) { const bgStartTime = this.startTime.getTime(); const bgEndTime = bgStartTime + 3600 * 24 * 1000; return [ { type: 'custom', renderItem: renderItem, itemStyle: { opacity: 0.8, }, encode: { x: [1, 2], y: 0, }, data: xdata, }, ] } init(data) { if (!this.el) throw new Error('没有可供echarts初始化的容器'); if (typeof this.el == 'string') { this.el = document.querySelector(this.el); } this.chart = echarts.init(this.el); this.handleProps(data); // 调试:打印关键数据 console.log('=== 调试信息 ==='); console.log('x轴范围', this.xAxis[0]?.min, '~', this.xAxis[0]?.max); console.log('y轴数据', this.yAxis[0]?.data); console.log('series数据', this.series[0]?.data); setTimeout(() => { this.chart.setOption(this.option); // 强制渲染 this.chart.resize(); }, 200); } update(data) { this.clear(); this.init(data); } resize() { this.chart.resize(); } get option() { return { tooltip: this.tooltip, grid: this.grid, xAxis: this.xAxis, yAxis: this.yAxis, series: this.series, dataZoom: [ { type: 'slider', xAxisIndex: 0, filterMode: 'weakFilter', height: 20, bottom: 0, start: 0, end: 80, handleIcon: 'path://M10.7,11.9H9.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z', handleSize: '80%', showDetail: false }, { type: 'inside', id: 'insideX', xAxisIndex: 0, filterMode: 'weakFilter', start: 0, end: 80, zoomOnMouseWheel: true, moveOnMouseMove: true }, { type: 'slider', yAxisIndex: 0, zoomLock: true, width: 10, right: 10, top: 70, bottom: 20, start: 0, end: 100, handleSize: 0, showDetail: false }, { type: 'inside', id: 'insideY', yAxisIndex: 0, start: 0, end: 100, zoomOnMouseWheel: true, moveOnMouseMove: true, moveOnMouseWheel: true } ], } } // 每次 graphList 刷新都会重新渲染整个所有图表 // 可以改进的地方:添加一个 handleAdd() 方法,一次添加一个新的 handleProps(props) { console.log('props: ', props); let ylist = [] let xdata = [] // 第一步:提前获取x轴的实际min/max(从xAxis配置中取,而非重新计算) const xAxisConfig = this.makeXaxis(props)[0]; const xMin = xAxisConfig.min; const xMax = xAxisConfig.max; props.forEach((eqArr, index) => { if (!eqArr.key) return; ylist.push(eqArr.key) eqArr.forEach(item => { if (!item.startTime || !item.duration || item.status === undefined) { console.warn('数据缺失', item); return; } const status = Math.max(0, Math.min(3, item.status)); const endTime = item.startTime + item.duration * 60 * 1000; // 第二步:修正时间校验逻辑(只要数据和x轴范围有交集,就保留) // 原逻辑:数据完全在x轴外才过滤 → 改为:数据和x轴无交集才过滤 const isOutOfRange = endTime < xMin || item.startTime > xMax; if (isOutOfRange) { console.warn('数据超出x轴范围', { startTime: new Date(item.startTime).toLocaleString(), endTime: new Date(endTime).toLocaleString(), xMin: new Date(xMin).toLocaleString(), xMax: new Date(xMax).toLocaleString() }); return; } xdata.push({ name: types[status].name, showName: eqArr.key, value: [index, item.startTime, endTime], itemStyle: { color: types[status].color, } }); }); }); this.grid.push(this.makeGrid()); this.xAxis.push(...this.makeXaxis(props)); // 传入props this.yAxis.push(...this.makeYaxis(ylist)); this.series.push(...this.makeSeries(xdata)); } // handleAdd handleAdd() { } clear() { this.grid = []; this.xAxis = []; this.yAxis = []; this.series = []; this.chart.dispose(); } // print option print() { console.log(JSON.stringify(this.option, null, 2)); } }