You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
412 lines
16 KiB
412 lines
16 KiB
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<Object>;
|
|
public showStartValue: boolean;
|
|
public showEndValue: boolean;
|
|
|
|
constructor(scales: Array<Object>, showStartValue: boolean = false, showEndValue: boolean = false){
|
|
this.scales = scales;
|
|
this.showEndValue = showEndValue;
|
|
this.showStartValue = showStartValue;
|
|
}
|
|
}
|
|
|
|
|