329 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			329 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import * as echarts from 'echarts'
 | |
| 
 | |
| function getStartTime(timestamp) {
 | |
|     return new Date(new Date(timestamp).toLocaleDateString()).getTime();
 | |
| }
 | |
| 
 | |
| function renderItem(params, api) {
 | |
|     var categoryIndex = api.value(0);
 | |
|     var start = api.coord([api.value(1), categoryIndex]);
 | |
|     var end = api.coord([api.value(2), categoryIndex]);
 | |
| 
 | |
|     var height = api.size([0, 1])[1] * 2;
 | |
|     var rectShape = echarts.graphic.clipRectByRect(
 | |
|         {
 | |
|             x: start[0],
 | |
|             y: start[1] - height / 2,
 | |
|             width: end[0] - start[0],
 | |
|             height: height,
 | |
|         },
 | |
|         {
 | |
|             x: params.coordSys.x,
 | |
|             y: params.coordSys.y - 16,
 | |
|             width: params.coordSys.width,
 | |
|             height: params.coordSys.height,
 | |
|         }
 | |
|     );
 | |
| 
 | |
|     return (
 | |
|         rectShape && {
 | |
|             type: 'rect',
 | |
|             transition: ['shape'],
 | |
|             shape: rectShape,
 | |
|             style: api.style(),
 | |
|         }
 | |
|     );
 | |
| }
 | |
| 
 | |
| // 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;
 | |
|             return `
 | |
|             <div style="display: flex; flex-direction: column;">
 | |
|                 <span>${new Date(params.value[1]).toLocaleTimeString()} ~ ${new Date(params.value[2]).toLocaleTimeString()}</span>
 | |
|                 <div style="display: flex; align-items: center; justify-content: space-between;">
 | |
|                     <div style="display: flex; align-items: center;">
 | |
|                         <span class="icon" style="width: 8px; height: 8px; border-radius: 2px; background: ${params.color}"></span>
 | |
|                         <span class="eq-name" style="margin-left: 4px;">${params.seriesName}</span>
 | |
|                     </div>
 | |
|                     <span class="run-status" style="margin-left: 8px; opacity: 0.6">${params.name}</span>
 | |
|             </div>
 | |
|             `
 | |
|         }
 | |
|     }
 | |
|     grid = []
 | |
|     xAxis = []
 | |
|     yAxis = []
 | |
|     series = []
 | |
| 
 | |
|     constructor(el, startTime) {
 | |
|         this.el = el;
 | |
|         this.gridIndex = -1;
 | |
|         this.currentGraphIndex = -2;
 | |
|         this.startTime = new Date(startTime);
 | |
|         // this.startTime = new Date(new Date('2023/10/8').toLocaleDateString());
 | |
|         // console.log('<> Gantt Created', this.startTime);
 | |
|     }
 | |
| 
 | |
|     // 构造一个新的 grid
 | |
|     makeGrid() {
 | |
|         this.gridIndex++;
 | |
|         return {
 | |
|             id: 'GRID_' + this.gridIndex,
 | |
|             // top: 12 + 128 * this.gridIndex,
 | |
|             top: 12 + 104 * this.gridIndex,
 | |
|             right: 48,
 | |
|             left: 88,
 | |
|             height: 56
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // 构造一个 xAxis
 | |
|     makeXaxis() {
 | |
|         const [id1, id2] = ['' + Math.random(), '' + Math.random()]
 | |
|         return [
 | |
|             {
 | |
|                 id: id1,
 | |
|                 gridIndex: this.gridIndex,
 | |
|                 axisTick: {
 | |
|                     alignWithLabel: true,
 | |
|                     inside: true,
 | |
|                 },
 | |
|                 type: 'time',
 | |
|                 min: getTodayStart(this.startTime),
 | |
|                 max: getStartTime(this.startTime.getTime() + 3600 * 24 * 1000),
 | |
|                 splitNumber: 10,
 | |
|                 axisLabel: {
 | |
|                     margin: 12,
 | |
|                     formatter: function (val) {
 | |
|                         return new Date(val)
 | |
|                             .toLocaleTimeString()
 | |
|                             .split(':')
 | |
|                             .slice(0, 2)
 | |
|                             .join(':');
 | |
|                     },
 | |
|                 },
 | |
|                 axisLine: {
 | |
|                     lineStyle: {
 | |
|                         color: '#0005',
 | |
|                     },
 | |
|                 },
 | |
|                 boundaryGap: false,
 | |
|                 // data: getXaxisRange(getTodayStart(new Date())),
 | |
|             },
 | |
|             {
 | |
|                 id: id2,
 | |
|                 gridIndex: this.gridIndex,
 | |
|                 axisLabel: { show: false },
 | |
|                 axisLine: { show: false },
 | |
|             },
 | |
|         ]
 | |
|     }
 | |
| 
 | |
| 
 | |
|     // 构造一个 yAxis
 | |
|     makeYaxis(equipmentName) {
 | |
|         const [id1, id2] = ['' + Math.random(), '' + Math.random()]
 | |
|         return [
 | |
|             // 主y轴
 | |
|             {
 | |
|                 id: id1,
 | |
|                 gridIndex: this.gridIndex,
 | |
|                 type: 'value',
 | |
|                 splitLine: { show: false },
 | |
|                 name: equipmentName,
 | |
|                 nameLocation: 'center',
 | |
|                 nameGap: 14,
 | |
|                 nameRotate: 0,
 | |
|                 nameTextStyle: {
 | |
|                     fontSize: 16,
 | |
|                     color: '#000A'
 | |
|                 },
 | |
|                 axisLine: {
 | |
|                     show: true,
 | |
|                     lineStyle: {
 | |
|                         color: '#0005',
 | |
|                     },
 | |
|                 },
 | |
|                 axisLabel: { show: false },
 | |
|                 axisTick: { show: false },
 | |
|             },
 | |
|             // 辅y轴
 | |
|             {
 | |
|                 id: id2,
 | |
|                 gridIndex: this.gridIndex,
 | |
|                 type: 'value',
 | |
|                 splitLine: { show: false },
 | |
|                 axisLine: {
 | |
|                     show: true,
 | |
|                     lineStyle: {
 | |
|                         color: '#0005',
 | |
|                     },
 | |
|                 },
 | |
|                 axisLabel: { show: false },
 | |
|                 axisTick: { show: false },
 | |
|             },
 | |
|         ]
 | |
|     }
 | |
| 
 | |
|     // 构造一个 series
 | |
|     makeSeries({ equipmentName, arr }) {
 | |
|         this.currentGraphIndex += 2;
 | |
|         const bgStartTime = this.startTime.getTime();
 | |
|         const bgEndTime = bgStartTime + 3600 * 24 * 1000;
 | |
|         return [
 | |
|             // 沉默的背景
 | |
|             {
 | |
|                 xAxisIndex: this.currentGraphIndex,
 | |
|                 yAxisIndex: this.currentGraphIndex,
 | |
|                 type: 'custom',
 | |
|                 renderItem: renderItem,
 | |
|                 silent: true,
 | |
|                 itemStyle: {
 | |
|                     opacity: 0.8,
 | |
|                 },
 | |
|                 encode: {
 | |
|                     x: [1, 2],
 | |
|                     y: 0,
 | |
|                 },
 | |
|                 data: [
 | |
|                     {
 | |
|                         name: '无数据',
 | |
|                         value: [0, bgStartTime, bgEndTime, 0],
 | |
|                         tooltip: { show: false },
 | |
|                         itemStyle: {
 | |
|                             color: '#F2F4F9',
 | |
|                             opacity: 0.3,
 | |
|                         }
 | |
|                     },
 | |
|                 ]
 | |
|             },
 | |
|             {
 | |
|                 name: equipmentName,
 | |
|                 xAxisIndex: this.currentGraphIndex,
 | |
|                 yAxisIndex: this.currentGraphIndex,
 | |
|                 type: 'custom',
 | |
|                 renderItem: renderItem,
 | |
|                 itemStyle: {
 | |
|                     opacity: 0.8,
 | |
|                 },
 | |
|                 encode: {
 | |
|                     x: [1, 2],
 | |
|                     y: 0,
 | |
|                 },
 | |
|                 data: arr.map(item => ({
 | |
|                     name: ['运行', '计划停机', '故障'][item.status],
 | |
|                     value: [0, item.startTime, item.startTime + item.duration * 60 * 1000, 0],
 | |
|                     itemStyle: {
 | |
|                         color: types[item.status].color,
 | |
|                     }
 | |
|                 })),
 | |
|             },
 | |
|         ]
 | |
|     }
 | |
| 
 | |
|     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);
 | |
| 
 | |
|         setTimeout(() => {
 | |
|             // debugger;
 | |
|             this.chart.setOption(this.option);
 | |
|         }, 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,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // 每次 graphList 刷新都会重新渲染整个所有图表
 | |
|     // 可以改进的地方:添加一个 handleAdd() 方法,一次添加一个新的
 | |
|     handleProps(props) {
 | |
|         // props 是父组件的 graphList
 | |
|         console.log('props: ', props);
 | |
|         props.forEach(eqArr => {
 | |
|             this.grid.push(this.makeGrid());
 | |
|             this.xAxis.push(...this.makeXaxis());
 | |
|             this.yAxis.push(...this.makeYaxis(eqArr.key));
 | |
|             this.series.push(...this.makeSeries({ equipmentName: eqArr.key, arr: eqArr }))
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     // handleAdd
 | |
|     handleAdd() { }
 | |
| 
 | |
|     clear() {
 | |
|         this.grid = [];
 | |
|         this.xAxis = [];
 | |
|         this.yAxis = [];
 | |
|         this.series = [];
 | |
|         this.currentGraphIndex = -2;
 | |
|         this.gridIndex = -1;
 | |
|         this.chart.dispose();
 | |
|     }
 | |
| 
 | |
|     // print option
 | |
|     print() {
 | |
|         console.log(JSON.stringify(this.option, null, 2));
 | |
|     }
 | |
| 
 | |
| }
 |