import moment from "moment"; import {h} from "vue"; import {urlRE} from "vite/dist/node/utils/cssUtils"; export class BoxDrawer{ private canvas: any = null; private canvasContext: any = null; private targetCanvas: any = null; private targetCanvasContext: any = null; // private canvasContext3d: any = null; private borderWidth: number = 0; private borderHeight: number = 0; private readonly width: number = null; private readonly height: number = null; private readonly paddingLeft: number = 100; private readonly paddingRight: number = 100; private readonly paddingTop: number = 50; private readonly paddingBottom: number = 50; private readonly colorChart: ColorChart = null; private readonly values: any = null; private readonly verticalScaleSpecialCount: number = 6; private readonly horizontalScaleSpecialCount: number = 6; // 垂直刻度 private readonly verticalScaleLine: number = 18; // 水平刻度 private readonly horizontalScaleLine: number = 20; public base64Image: string = null; private verticalScaleIntervalLength: number = 0; private horizontalScaleIntervalLength: number = 0; private paddingColorLeft: number = 50; private defaultColorWidth: number = 30; // private boxes: any = []; private calc: Calc = null; private box: Box = null; constructor(width: number, height: number, colorChart: ColorChart, values: any, id: string) { if (id == null) return; this.width = width; this.height = height; this.colorChart = colorChart; this.values = values; this.init(); this.drawCoordinate(); this.drawColorChart(); this.draw(); this.setTargetCanvas(id); this.base64Image = this.canvas.toDataURL(); } private drawColorChart(): void { let startX = this.paddingLeft + this.horizontalScaleLine + this.borderWidth + this.paddingColorLeft; let heightInterval = 0; let startY = this.paddingTop + this.verticalScaleLine + heightInterval; let interval = (this.borderHeight - heightInterval) / (this.colorChart.colors.length) this.canvasContext.font = "normal 18px Verdana"; this.canvasContext.fillStyle = "#000000"; for(let lastLen = this.colorChart.colors.length - 1, index = lastLen; index >= 0; index --){ const targetIndex = lastLen - index; const _y = startY + index * interval; this.canvasContext.fillStyle = this.colorChart.colors[targetIndex]; this.canvasContext.fillRect(startX, _y, this.defaultColorWidth, interval); this.canvasContext.fillStyle = "#000000"; if (index == 0){ if (this.colorChart.showStartValue){ const text = this.colorChart.values[targetIndex + 1]; this.canvasContext.fillText(text, startX + this.defaultColorWidth, _y + 9); } const text = this.colorChart.values[targetIndex]; this.canvasContext.fillText(text, startX + this.defaultColorWidth, _y + interval + 9); }else if (index == lastLen){ if (this.colorChart.showEndValue){ const text = this.colorChart.values[targetIndex]; this.canvasContext.fillText(text, startX + this.defaultColorWidth, _y + interval + 9); } const text = this.colorChart.values[targetIndex + 1]; this.canvasContext.fillText(text, startX + this.defaultColorWidth, _y + 9); }else{ const text = this.colorChart.values[targetIndex]; this.canvasContext.fillText(text, startX + this.defaultColorWidth, _y + interval + 9); } } } private draw(): void{ let startX = this.paddingLeft + this.horizontalScaleLine - this.verticalScaleIntervalLength / 2; let startY = this.paddingTop + this.verticalScaleLine - this.horizontalScaleIntervalLength / 2; let boxes = []; for(let dataIndex = 0, len = this.values.radar_data.length, lastDataLen = len - 1; dataIndex < len; dataIndex++){ let value = this.values.radar_data[dataIndex]; let rows = []; for(let lastInfoLen = this.values.radar_info.length - 1, infoIndex = lastInfoLen; infoIndex >= 0; infoIndex--){ let x = startX + dataIndex * this.verticalScaleIntervalLength; let y = startY + (this.borderHeight - infoIndex * this.horizontalScaleIntervalLength); let width = this.verticalScaleIntervalLength; let height = this.horizontalScaleIntervalLength; if (dataIndex == 0) { x = x + this.verticalScaleIntervalLength / 2; width = width / 2; } if (infoIndex == lastInfoLen){ y = y + this.horizontalScaleIntervalLength / 2; height = height / 2; } if (dataIndex == lastDataLen){ width = width / 2; } if (infoIndex == 0){ height = height / 2; } let radarInfo = this.values.radar_info[infoIndex]; let targetValue = value[radarInfo.col_name]; let color = this.colorChart.getColor(targetValue); if (color == null) continue; this.canvasContext.fillStyle = color; this.canvasContext.fillRect(x, y, width, height); rows.push(new Box(x, y, width, height, targetValue, radarInfo.col_factor, value.data_time, radarInfo.col_unit)); } boxes.push(rows); } this.createCalc(boxes); } private createCalc(boxes: any): void{ this.calc = new Calc(this.width, this.height, this.paddingLeft + this.verticalScaleLine, this.paddingTop + this.horizontalScaleLine, this.paddingRight + this.horizontalScaleLine, this.paddingBottom + this.verticalScaleLine, boxes); } private resetValues() : void{ this.values.radar_data.sort((a, b) => { return moment(a.data_time).isBefore(b.data_time, 'second'); }); this.values.radar_info.sort((a, b) => { return a.col_factor - b.col_factor; }); } private init(): void{ // 创建画布 this.canvas = document.createElement('canvas'); this.canvas.width = this.width; this.canvas.height = this.height; this.canvasContext = this.canvas.getContext('2d'); this.fillBackground(); this.resetValues(); } private setTargetCanvas(id): void{ this.targetCanvas = document.getElementById(id); this.targetCanvas.width = this.width; this.targetCanvas.height = this.height; this.targetCanvasContext = this.targetCanvas.getContext('2d'); // canvasContext.drawImage(this.canvas, 0, 0, 1500, 500); this.copyCanvas(); this.bindMoveEvent(); } private copyCanvas(): void{ const imgData= this.canvasContext.getImageData(0,0, this.width, this.height); this.targetCanvasContext.putImageData(imgData, 0, 0); } private bindMoveEvent(): void{ this.targetCanvas.addEventListener('mousemove', (e) => { let box = this.calc.calc(e.offsetX, e.offsetY); if (box == null) { if (this.box != null){ this.copyCanvas(); this.box = null; } return; } if (this.box != null){ if (this.box.id == box.id) return; this.copyCanvas(); this.box = null; } box.show(this.targetCanvasContext); this.box = box; }) } private fillBackground(): void{ this.canvasContext.fillStyle = 'white' this.canvasContext.fillRect(0,0, this.width,this.height) } private drawCoordinate(): void{ this.drawBorder(); this.drawVerticalScale(); this.drawHorizontalScale(); } private drawVerticalScale(): void{ let scaleCount = this.values.radar_data.length; this.verticalScaleIntervalLength = this.borderWidth / (scaleCount - 1); let scaleSpecial = parseInt((scaleCount * 1.0 / this.verticalScaleSpecialCount).toString()); let startX = this.paddingLeft + this.horizontalScaleLine; let startY = this.paddingTop + this.verticalScaleLine; for(let index = 0, len = scaleCount, lastLen = scaleCount - 1; index < len; index++){ if (index == 0 || index == lastLen) continue; const flag = index % scaleSpecial == 0; this.setScaleStyle(flag); const yInterval = flag ? this.verticalScaleLine : this.verticalScaleLine / 2; const _x = startX + index * this.verticalScaleIntervalLength; this.canvasContext.beginPath(); this.canvasContext.moveTo(_x, startY); this.canvasContext.lineTo(_x,startY - yInterval); this.canvasContext.moveTo(_x, startY + this.borderHeight); this.canvasContext.lineTo(_x,startY + yInterval + this.borderHeight); this.canvasContext.stroke(); if (flag){ this.canvasContext.font = "normal 20px Verdana"; this.canvasContext.fillStyle = "#000000"; const text = moment(this.values.radar_data[index].data_time).format("HH:mm"); this.canvasContext.fillText(text, _x - this.canvasContext.measureText(text).width / 2, startY + yInterval + this.borderHeight + 36); } } } private drawHorizontalScale(): void{ let scaleCount = this.values.radar_info.length; this.horizontalScaleIntervalLength = this.borderHeight / (scaleCount - 1); let scaleSpecial = parseInt((scaleCount * 1.0 / this.horizontalScaleSpecialCount).toString()); let startX = this.paddingLeft + this.horizontalScaleLine; let startY = this.paddingTop + this.verticalScaleLine; const lastLen = scaleCount - 1; for(let index = lastLen; index >= 0; index--){ const flag = index % scaleSpecial == 0; this.setScaleStyle(flag); const xInterval = flag? this.horizontalScaleLine : this.horizontalScaleLine / 2; const _y = startY + (this.borderHeight - index * this.horizontalScaleIntervalLength); this.canvasContext.beginPath(); this.canvasContext.moveTo(startX, _y); this.canvasContext.lineTo(startX - xInterval, _y); this.canvasContext.moveTo(this.borderWidth + startX, _y); this.canvasContext.lineTo(this.borderWidth + startX + xInterval, _y); this.canvasContext.stroke(); if (flag){ this.canvasContext.font = "normal 20px Verdana"; this.canvasContext.fillStyle = "#000000"; const text = this.values.radar_info[index].col_factor; this.canvasContext.fillText(text, startX - this.canvasContext.measureText(text).width - this.horizontalScaleLine, _y + 10); } } } private setScaleStyle(isSpecial: boolean): void{ if (isSpecial){ this.canvasContext.lineWidth = 3; this.canvasContext.strokeStyle = '#101010'; return; } this.canvasContext.lineWidth = 2; this.canvasContext.strokeStyle = '#5e5e5e'; } private drawBorder(): void{ const x = this.horizontalScaleLine + this.paddingLeft; const y = this.verticalScaleLine + this.paddingTop; const _x = this.width - this.horizontalScaleLine - this.paddingRight; const _y = this.height - this.verticalScaleLine - this.paddingBottom; this.canvasContext.beginPath(); this.canvasContext.lineWidth = "2"; this.canvasContext.strokeStyle = "black"; this.borderWidth = _x - x; this.borderHeight = _y - y; this.canvasContext.rect(x, y, this.borderWidth, this.borderHeight); this.canvasContext.stroke(); } } export class ColorChart{ public colors: any = null; public values: any = null; public showStartValue: boolean = false; public showEndValue: boolean = false; private readonly valueLength: number = 0; constructor(colors: any, values: any, showStartValue: boolean = false, showEndValue: boolean = false) { this.verify(colors, values); this.colors = colors; this.values = values; this.showStartValue = showStartValue; this.showEndValue = showEndValue; this.valueLength = this.values.length; this.resetValues(); } private resetValues(): void{ if (this.values[0] < this.values[1]) return; this.values = this.values.reverse(); this.colors = this.colors.reverse(); } private verify(colors: any, values: any): void{ if (values.length <= 2) throw new Error("色标数据至少包含两个数据"); if (colors.length != values.length - 1) throw new Error("色标数据不正确,数据个数应为颜色个数 + 1"); } public getColor(value: number): string{ for(let index = 1; index < this.valueLength; index ++){ if (this.values[index -1] <= value && this.values[index] > value){ return this.colors[index - 1]; } } return null; } } interface IBox { show(canvasContext: any); } class Box implements IBox{ id: string = null; x: number; y: number; width: number; height: number; value: number; dataHeight: number; time: string; util: string; rectWidth: number = 180; rectHeight: number = 80; rectX: number; rectY: number; constructor(x: number, y: number, width: number, height: number, value: number, dataHeight: number, time: string, util: string) { this.x = x; this.y = y; this.width = width; this.height = height; this.value = value; this.dataHeight = dataHeight; this.time = time; this.util = util; this.id = this.uuid(); } show(canvasContext: any) { this.setSelectStyle(canvasContext); this.setTip(canvasContext); } private setTip(canvasContext: any): void{ this.setBackground(canvasContext); this.setText(canvasContext); } private setBackground(canvasContext: any): void{ canvasContext.fillStyle = 'rgba(59, 55, 55, 0.8)'; this.rectX = this.x + this.width / 2 - this.rectWidth / 2; this.rectY = this.y - this.height / 2 - this.rectHeight - 10; canvasContext.fillRect(this.rectX, this.rectY, this.rectWidth, this.rectHeight); } private setText(canvasContext: any) : void{ canvasContext.font = "normal 14px Arial"; canvasContext.fillStyle = "#fafafa"; const centerX = this.rectX + this.rectWidth / 2; const timeText = "time:" + this.time; canvasContext.fillText(timeText, centerX - canvasContext.measureText(timeText).width / 2, this.rectY + 22); const valueText = 'value:' + this.value; canvasContext.fillText(valueText, centerX - canvasContext.measureText(valueText).width / 2, this.rectY + 18 + 26); const heightText = 'height:' + this.dataHeight + ' ' + this.util; canvasContext.fillText(heightText, centerX - canvasContext.measureText(heightText).width / 2, this.rectY + 18 + 18 + 30); } private setSelectStyle(canvasContext: any): void{ canvasContext.lineWidth = 1; canvasContext.strokeStyle = "#ff0000"; canvasContext.strokeRect(this.x, this.y, this.width, this.height); } uuid(): string{ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { let r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); return v.toString(16); }); } } class Calc{ private readonly width: number; private readonly height: number; private readonly paddingLeft: number; private readonly paddingTop: number; private readonly paddingRight: number; private readonly paddingBottom: number; private readonly boxWidth: number; private readonly boxHeight: number; private xMax: number = null; private yMax: number = null; private startX: number = null; private startY: number = null; private readonly boxes: any = null; constructor(width: number, height: number, paddingLeft: number, paddingTop: number, paddingRight: number, paddingBottom: number, boxes: any) { this.width = width; this.height = height; this.paddingLeft = paddingLeft; this.paddingTop = paddingTop; this.paddingRight = paddingRight; this.paddingBottom = paddingBottom; this.boxes = boxes; this.boxWidth = this.boxes[1][1].width; this.boxHeight = this.boxes[1][1].height; this.init(); } private init(): void{ this.xMax = this.width - this.paddingRight; this.yMax = this.height - this.paddingBottom; this.startX = this.paddingLeft - this.boxWidth / 2; this.startY = this.paddingTop - this.boxHeight / 2; } public calc(x: number, y: number): Box{ if (x <= this.paddingLeft || x >= this.xMax || y <= this.paddingTop || y >= this.yMax){ return null; } const indexX = parseInt(((x - this.startX) * 1.0 / this.boxWidth).toString()); const indexY = parseInt(((y - this.startY) * 1.0 / this.boxHeight).toString()) return this.boxes[indexX][indexY]; } }