import { number } from "echarts"; import moment from "moment"; export function createEmptyCanvas(id: string, width: number, height: number): void{ let canvas: any = document.getElementById(id); canvas.width = width; canvas.height = height; let canvasContext: any = canvas.getContext('2d'); canvasContext.fillStyle = 'white'; canvasContext.fillRect(0,0, width, height); canvasContext.font = "normal 48px 微软雅黑"; canvasContext.fillStyle = "#000000"; canvasContext.fillText('暂无数据', (width / 2) - (canvasContext.measureText('暂无数据').width / 2), height / 2 - 24); } export class BoxDrawer{ private canvas: any = null; private canvasContext: any = null; private targetCanvas: any = null; private targetCanvasContext: any = null; private borderWidth: number = 0; private borderHeight: number = 0; private readonly width: number = null; private readonly height: number = null; private readonly paddingLeft: number = 110; private readonly paddingRight: number = 110; private readonly paddingTop: number = 60; 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 = 25; private defaultColorWidth: number = 30; private calc: Calc = null; private box: Box = null; constructor(width: number, height: number, colorChart: ColorChart, values: any, id: string, unit: string) { if (id == null) return; this.width = width; this.height = height; this.colorChart = colorChart; this.values = values; this.init(); this.drawCoordinate(unit); 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 24px 微软雅黑"; 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); rows.push(new Box(x, y, width, height, targetValue, radarInfo.col_factor, value.data_time, radarInfo.col_unit)); if (color == null) continue; this.canvasContext.fillStyle = color; this.canvasContext.fillRect(x, y, width, height); } 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'); 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 _onMouseMove = this.onMouseMove.bind(this); private _onMouseOut = this.onMouseOut.bind(this) private bindMoveEvent(): void{ this.targetCanvas.addEventListener('mousemove', this._onMouseMove); this.targetCanvas.addEventListener('mouseout', this._onMouseOut); } private onMouseOut() : void{ this.copyCanvas(); } private onMouseMove(e) : void{ 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; } public close(): void{ this.targetCanvas.removeEventListener('mousemove', this._onMouseMove); this.targetCanvas.removeEventListener('mouseout', this._onMouseOut); } private fillBackground(): void{ this.canvasContext.fillStyle = 'white'; this.canvasContext.fillRect(0,0, this.width,this.height); } private drawCoordinate(unit): void{ this.drawBorder(); this.drawVerticalScale(); this.drawHorizontalScale(); this.drawHeightText(); this.drawTemperatureText(unit); } //高度单位 private drawHeightText(): void { this.canvasContext.save(); let name = "高度(km)"; this.canvasContext.font="normal 22px 微软雅黑"; this.canvasContext.translate(70, this.height / 2 + this.paddingTop); this.canvasContext.rotate(Math.PI * 1.5); this.canvasContext.fillStyle="#000000"; this.canvasContext.fillText(name, 0, 0); this.canvasContext.restore(); // let name =['高','度','(','k','m',')']; // 文本内容 // let x = 70,y = 150; // 文字开始的坐标 // let letterSpacing = 20; // 设置字间距 // for(let i = 0; i < name.length; i++) { // const str = name.slice(i, i + 1).toString(); // if (str.match(/[A-Za-z0-9-()-]/) && (y < 576)) { // 非汉字 旋转 // this.canvasContext.save(); // this.canvasContext.translate(x, y); // this.canvasContext.rotate(Math.PI / 180 * 90); // this.canvasContext.textBaseline = 'bottom'; // this.canvasContext.font = "normal 35px 微软雅黑"; // this.canvasContext.fillStyle = "#000000"; // this.canvasContext.fillText(str, 0, 0); // this.canvasContext.restore(); // y += this.canvasContext.measureText(str).width + letterSpacing; // 计算文字宽度 // } else if (str.match(/[\u4E00-\u9FA5]/) && (y < 576)) { // this.canvasContext.save(); // this.canvasContext.textBaseline = 'top'; // this.canvasContext.font = "normal 35px 微软雅黑"; // this.canvasContext.fillStyle = "#000000"; // this.canvasContext.fillText(str, x, y); // this.canvasContext.restore(); // y += this.canvasContext.measureText(str).width + letterSpacing; // 计算文字宽度 // } // } } //温度单位 private drawTemperatureText(unit): void { this.canvasContext.font = "normal 35px 微软雅黑"; this.canvasContext.fillStyle = "#000000"; let unitX = this.width - this.paddingRight - 15; if (unit === '(degree)') unitX = this.width - this.paddingRight - 40; this.canvasContext.fillText(unit, unitX,60); } 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 25px 微软雅黑"; 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 + 1).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 25px 微软雅黑"; 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 readonly type: string; public colors: any = null; public values: any = null; public showStartValue: boolean = false; public showEndValue: boolean = false; private minDefaultColor: string = null; private maxDefaultColor: string = null; private readonly valueLength: number = 0; constructor(colors: any, values: any, showStartValue: boolean = false, showEndValue: boolean = false, type: string = 'normal') { this.type = type; this.verify(colors, values); this.colors = colors; this.values = values; this.showStartValue = showStartValue; this.showEndValue = showEndValue; this.valueLength = this.values.length; this.resetValues(); } public setDefaultColors(minDefaultColor: string, maxDefaultColor: string): void{ this.minDefaultColor = minDefaultColor; this.maxDefaultColor = maxDefaultColor; } // 按照数值从小到大排序,供颜色值判断使用 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 (this.type == "equal") return; if (values.length <= 2) throw new Error("色标数据至少包含两个数据"); if (colors.length != values.length - 1) throw new Error("色标数据不正确,数据个数应为颜色个数 + 1"); } public getColor(value: number ): string{ if (isNaN(value)) return null; const length = this.valueLength; if (this.type == 'equal') { if (this.minDefaultColor != null){ if (value < this.values[0].value) return this.minDefaultColor; if (value > this.values[length - 1].value) return this.maxDefaultColor; } for(let index = 1; index < length; index ++){ if (this.values[index -1].value < value && this.values[index].value >= value){ return this.colors[index]; } } } if (this.minDefaultColor != null){ if (value < this.values[0]) return this.minDefaultColor; if (value > this.values[length - 1]) return this.maxDefaultColor; } for(let index = 1; index < length; index ++){ if (this.values[index -1] < value && this.values[index] >= value){ return this.colors[index - 1]; } } return null; } } export interface IBox { show(canvasContext: any); } export class Box implements IBox{ id: string = null; x: number; y: number; width: number; height: number; public value: number; public dataHeight: number; time: string; util: string; //提示框的宽高 rectWidth: number = 270; rectHeight: number = 90; rectX: number; rectY: number; isReserved: boolean = false; 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.isReserved = this.y - 10 - this.rectHeight < 0; this.id = this.uuid(); } updateBorderInfo(x: number, y: number, width: number, height: number): void{ this.x = x; this.y = y; this.width = width; this.height = height; this.isReserved = this.y - 10 - this.rectHeight < 0; } 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.isReserved ? this.y + this.height / 2 + 10 : this.y - this.height / 2 - this.rectHeight - 10; this.fillRoundRect(canvasContext, this.rectX, this.rectY, this.rectWidth, this.rectHeight, 10, 2, 'rgba(59, 55, 55, 0.8)'); } /**该方法用来绘制圆角矩形 *@param cxt:canvas的上下文环境 *@param x:左上角x轴坐标 *@param y:左上角y轴坐标 *@param width:矩形的宽度 *@param height:矩形的高度 *@param radius:圆的半径 *@param lineWidth:线条粗细 *@param strokeColor:线条颜色 *@user: xiaowuler **/ private fillRoundRect(cxt, x, y, width, height, radius, lineWidth, strokeColor): void { //圆的直径必然要小于矩形的宽高 if (2 * radius > width || 2 * radius > height) { return; } cxt.save(); cxt.translate(x, y); //绘制圆角矩形的各个边 this.drawRoundRectPath(cxt, width, height, radius); cxt.lineWidth = lineWidth || 2; //若是给定了值就用给定的值否则给予默认值2 cxt.fillStyle = strokeColor || "#000"; cxt.fill(); cxt.restore(); } private drawRoundRectPath(cxt, width, height, radius): void { cxt.beginPath(); //从右下角顺时针绘制,弧度从0到1/2PI cxt.arc(width - radius, height - radius, radius, 0, Math.PI / 2); //矩形下边线 cxt.lineTo(radius, height); //左下角圆弧,弧度从1/2PI到PI cxt.arc(radius, height - radius, radius, Math.PI / 2, Math.PI); //矩形左边线 cxt.lineTo(0, radius); //左上角圆弧,弧度从PI到3/2PI cxt.arc(radius, radius, radius, Math.PI, Math.PI * 3 / 2); //上边线 cxt.lineTo(width - radius, 0); //右上角圆弧 cxt.arc(width - radius, radius, radius, Math.PI * 3 / 2, Math.PI * 2); //右边线 cxt.lineTo(width, height - radius); cxt.closePath(); } private setText(canvasContext: any) : void{ canvasContext.font = "normal 20px 微软雅黑"; 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 + 25); const valueText = 'value:' + this.value.toPrecision(3); canvasContext.fillText(valueText, centerX - canvasContext.measureText(valueText).width / 2, this.rectY + 8 + 45); const heightText = 'height:' + this.dataHeight + ' ' + this.util; canvasContext.fillText(heightText, centerX - canvasContext.measureText(heightText).width / 2, this.rectY + 8 + 12 + 60); } private setSelectStyle(canvasContext: any): void{ canvasContext.lineWidth = 2; 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); }); } } export 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 boxWidth: number; private 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.setWidthAndHeight(); this.init(); } private setWidthAndHeight() : void{ for(let row = 0, rowLen = this.boxes.length; row < rowLen; row++){ for(let col = 0, colLen = this.boxes[row].length; col < colLen; col++){ let box = this.boxes[row][col]; if (box == null) continue; this.boxWidth = box.width; this.boxHeight = box.height; return; } } } 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]; } }