664 lines
25 KiB

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];
}
}