import { Box, Calc, ColorChart } from "../uilts/box-drawer"; export class HeatMapDrawer{ private readonly id: string; 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 = 100; private readonly paddingRight: number = 115; private readonly paddingTop: number = 50; private readonly paddingBottom: number = 100; private colorChart: ColorChart = null; private values: any = null; // 垂直刻度 private readonly verticalScaleLine: number = 15; // 水平刻度 private readonly horizontalScaleLine: number = 15; public base64Image: string = null; private verticalScaleIntervalLength: number = 0; private horizontalScaleIntervalLength: number = 0; private paddingColorLeft: number = 20; private defaultColorWidth: number = 30; private calc: Calc = null; private box: Box = null; private xAxis: CoordinateScale; private yAxis: CoordinateScale; private unit: string; private name: string; constructor(width: number, height: number, values: any, id: string, unit: string = '', name: string = '') { if (id == null) { throw new Error("heat map drawer id not allow null"); } this.id = id; this.width = width; this.height = height; this.values = values; this.unit = unit; this.name = name; this.init(); } public setAxis(xAxis: CoordinateScale, yAxis: CoordinateScale): void { this.xAxis = xAxis; this.yAxis = yAxis; } public setColorChart(colorChart: ColorChart) : void { this.colorChart = colorChart; } public 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.lineWidth="1"; this.canvasContext.strokeStyle="black"; this.canvasContext.rect(startX, startY, this.defaultColorWidth, this.borderHeight - heightInterval); this.canvasContext.stroke(); this.canvasContext.font = "normal 20px 微软雅黑"; 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 (this.colorChart.type == 'equal') { const text = this.colorChart.values[targetIndex].label; this.canvasContext.fillText(text, startX + this.defaultColorWidth + 2, _y + interval - 40); continue; } if (index == 0){ if (this.colorChart.showStartValue){ const text = this.colorChart.values[targetIndex + 1]; this.canvasContext.fillText(text, startX + this.defaultColorWidth + 2, _y + 9); } const text = this.colorChart.values[targetIndex]; this.canvasContext.fillText(text, startX + this.defaultColorWidth + 2, _y + interval + 9); }else if (index == lastLen){ if (this.colorChart.showEndValue){ const text = this.colorChart.values[targetIndex]; this.canvasContext.fillText(text, startX + this.defaultColorWidth + 2, _y + interval + 9); } const text = this.colorChart.values[targetIndex + 1]; this.canvasContext.fillText(text, startX + this.defaultColorWidth + 2, _y + 9); }else{ const text = this.colorChart.values[targetIndex]; this.canvasContext.fillText(text, startX + this.defaultColorWidth + 2, _y + interval + 9); } } } public draw(): void{ this.drawCoordinate(); this.drawColorChart(); this.redraw(); this.setTargetCanvas(this.id); } private calcDataScale() : void{ this.verticalScaleIntervalLength = this.borderWidth / (this.values.length - 1); this.horizontalScaleIntervalLength = this.borderHeight / (this.values[0].length - 1); } private redraw(): void{ this.calcDataScale(); let startX = this.paddingLeft + this.horizontalScaleLine - this.verticalScaleIntervalLength / 2; let startY = this.paddingTop + this.verticalScaleLine - this.horizontalScaleIntervalLength / 2; for(let dataIndex = 0, len = this.values.length, lastDataLen = len - 1; dataIndex < len; dataIndex++){ let value = this.values[dataIndex]; for(let lastInfoLen = value.length - 1, infoIndex = lastInfoLen; infoIndex >= 0; infoIndex--){ if (value[infoIndex] == null) continue; 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 targetValue = value[infoIndex].value; let color = this.colorChart.getColor(targetValue); value[infoIndex].updateBorderInfo(x, y, width, height); if (color == null) continue; this.canvasContext.fillStyle = color; this.canvasContext.fillRect(x, y, width, height); } // this.values[dataIndex].sort((x, y) => y.dataHeight - x.dataHeight); this.values[dataIndex] = this.values[dataIndex].reverse(); } this.createCalc(this.values); } 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 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.values.length = 0; if (this.targetCanvas == null) return; 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); } public drawCoordinate(): void{ this.drawBorder(); this.drawVerticalScale(); this.drawHorizontalScale(); this.drawHeightText(); this.drawTemperatureText(this.unit); this.drawResolutionName(this.name) } //高度单位 private drawHeightText(): void { // 保存原有的canvas状态,供restore方法重置 this.canvasContext.save(); let name = "高度(m)"; this.canvasContext.translate(45, this.height / 2 + this.paddingTop - this.canvasContext.measureText(name).width); this.canvasContext.rotate(Math.PI * 1.5); this.canvasContext.font="normal 22px 微软雅黑"; this.canvasContext.fillStyle="#000000"; this.canvasContext.fillText(name, 0, 0); this.canvasContext.restore(); // let name =['高','度','(','m',')']; // 文本内容 // let x = 70,y = 180; // 文字开始的坐标 // let letterSpacing = 10; // 设置字间距 // 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 20px 微软雅黑"; // 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 20px 微软雅黑"; // 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; this.canvasContext.fillText(unit, unitX, 45); } private drawResolutionName(name): void { this.canvasContext.font = "normal 35px 微软雅黑"; this.canvasContext.fillStyle = "#000000"; let nameY = this.paddingTop - 23 this.canvasContext.fillText(name, 380, nameY) } private drawVerticalScale(): void{ let scaleCount = this.xAxis.scales.length; let verticalScaleIntervalLength = this.borderWidth / (scaleCount - 1); let startX = this.paddingLeft + this.horizontalScaleLine; let startY = this.paddingTop + this.verticalScaleLine; this.canvasContext.font = "normal 25px 微软雅黑"; this.canvasContext.fillStyle = "#000000"; for(let index = 0, len = scaleCount, lastLen = scaleCount - 1; index < len; index++){ if ((index == 0 && !this.xAxis.showStartValue) || (index == lastLen && !this.xAxis.showEndValue)) continue; this.setScaleStyle(true); const _x = startX + index * verticalScaleIntervalLength; this.canvasContext.beginPath(); this.canvasContext.moveTo(_x, startY); this.canvasContext.lineTo(_x,startY - this.verticalScaleLine); this.canvasContext.moveTo(_x, startY + this.borderHeight); this.canvasContext.lineTo(_x,startY + this.verticalScaleLine + this.borderHeight); this.canvasContext.stroke(); const currentText = this.xAxis.scales[index]; if (index == 1 || ((currentText == '00' || currentText == '0') && index > 3)){ this.setTimeDetail(_x, startY, this.values[index * 11 + 2][5].time.slice(0, 6)); } this.canvasContext.fillText(currentText, _x - this.canvasContext.measureText(currentText).width / 2, startY + this.verticalScaleLine + this.borderHeight + 20); } } private setTimeDetail(_x: number, startY: number, text: string): void{ // const text = this.values[15][0].time.slice(0, 6); this.canvasContext.font = "normal 15px 微软雅黑"; this.canvasContext.fillText(text, _x - this.canvasContext.measureText(text).width / 2, startY + this.verticalScaleLine + this.borderHeight + 38); this.canvasContext.font = "normal 17px 微软雅黑"; } private drawHorizontalScale(): void{ let scaleCount = this.yAxis.scales.length; let horizontalScaleIntervalLength = this.borderHeight / (scaleCount - 1); let startX = this.paddingLeft + this.horizontalScaleLine; let startY = this.paddingTop + this.verticalScaleLine; const lastLen = scaleCount - 1; for(let index = lastLen; index >= 0; index--) { if ((index == 0 && !this.yAxis.showStartValue) || (index == lastLen && !this.yAxis.showEndValue)) continue; this.setScaleStyle(true); const _y = startY + (this.borderHeight - index * horizontalScaleIntervalLength); this.canvasContext.beginPath(); this.canvasContext.moveTo(startX, _y); this.canvasContext.lineTo(startX - this.horizontalScaleLine, _y); this.canvasContext.moveTo(this.borderWidth + startX, _y); this.canvasContext.lineTo(this.borderWidth + startX + this.horizontalScaleLine, _y); this.canvasContext.stroke(); if (true) { this.canvasContext.font = "normal 20px 微软雅黑"; this.canvasContext.fillStyle = "#000000"; this.canvasContext.fillText(this.yAxis.scales[index], startX - this.canvasContext.measureText(this.yAxis.scales[index]).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 CoordinateScale { public scales: Array; public showStartValue: boolean; public showEndValue: boolean; constructor(scales: Array, showStartValue: boolean = false, showEndValue: boolean = false){ this.scales = scales; this.showEndValue = showEndValue; this.showStartValue = showStartValue; } }