8 changed files with 694 additions and 3847 deletions
File diff suppressed because it is too large
@ -0,0 +1,20 @@ |
|||
/** |
|||
* 重写 数组 push 函数 |
|||
*/ |
|||
export class CustomeArray<T> extends Array<T>{ |
|||
|
|||
private callBack: any = null; |
|||
private params: any = null; |
|||
|
|||
constructor(callBack: any, ...params: any){ |
|||
super(); |
|||
this.callBack = callBack; |
|||
this.params = params; |
|||
} |
|||
|
|||
push(item: T): number { |
|||
const index = super.push(item); |
|||
this.callBack(this, this.params); |
|||
return index; |
|||
} |
|||
} |
@ -0,0 +1,367 @@ |
|||
import moment from "moment"; |
|||
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 = 200; |
|||
private readonly paddingRight: number = 100; |
|||
private readonly paddingTop: number = 60; |
|||
private readonly paddingBottom: number = 50; |
|||
private colorChart: ColorChart = null; |
|||
private values: any = null; |
|||
private readonly verticalScaleSpecialCount: number = 1; |
|||
private readonly horizontalScaleSpecialCount: number = 1; |
|||
|
|||
// 垂直刻度
|
|||
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 calc: Calc = null; |
|||
private box: Box = null; |
|||
|
|||
private xAxis: CoordinateScale; |
|||
private yAxis: CoordinateScale; |
|||
|
|||
private unit: string; |
|||
|
|||
constructor(width: number, height: number, values: any, id: string, unit: string) { |
|||
if (id == null) return; |
|||
|
|||
this.id = id; |
|||
this.width = width; |
|||
this.height = height; |
|||
this.values = values; |
|||
this.unit = unit; |
|||
|
|||
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.font = "normal 24px 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); |
|||
} |
|||
} |
|||
} |
|||
|
|||
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 = this.values[0].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 targetValue = value[infoIndex].value; |
|||
let color = this.colorChart.getColor(targetValue); |
|||
value[infoIndex].updateBorderInfo(width, height); |
|||
if (color == null) continue; |
|||
this.canvasContext.fillStyle = color; |
|||
this.canvasContext.fillRect(x, y, width, height); |
|||
} |
|||
// 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.targetCanvas.removeEventListener('mousemove', this._onMouseMove); |
|||
this.targetCanvas.removeEventListener('mouseout', this._onMouseOut); |
|||
this.values.length = 0; |
|||
} |
|||
|
|||
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); |
|||
} |
|||
|
|||
//高度单位
|
|||
private drawHeightText(): void { |
|||
let name =['高','度','(','m',')']; // 文本内容
|
|||
let x = 70,y = 180; // 文字开始的坐标
|
|||
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 Verdana"; |
|||
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 Verdana"; |
|||
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 Verdana"; |
|||
this.canvasContext.fillStyle = "#000000"; |
|||
let unitX = 1390 |
|||
if (unit === '(degree)') unitX =1350 |
|||
this.canvasContext.fillText(unit, unitX, 50); |
|||
} |
|||
|
|||
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; |
|||
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(); |
|||
|
|||
if (true){ |
|||
this.canvasContext.font = "normal 30px Verdana"; |
|||
this.canvasContext.fillStyle = "#000000"; |
|||
this.canvasContext.fillText(this.xAxis.scales[index], _x - this.canvasContext.measureText(this.xAxis.scales[index]).width / 2, startY + this.verticalScaleLine + this.borderHeight + 36); |
|||
} |
|||
} |
|||
} |
|||
|
|||
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 30px Verdana"; |
|||
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<number>; |
|||
public showStartValue: boolean; |
|||
public showEndValue: boolean; |
|||
|
|||
constructor(scales: Array<number>, showStartValue: boolean = false, showEndValue: boolean = false){ |
|||
this.scales = scales; |
|||
this.showEndValue = showEndValue; |
|||
this.showStartValue = showStartValue; |
|||
} |
|||
} |
|||
|
@ -0,0 +1,133 @@ |
|||
import * as Highcharts from "highcharts" |
|||
// Highcharts.heatmap(Highcharts);
|
|||
|
|||
export class HighChartHeatMapCreate { |
|||
|
|||
private readonly id: string; |
|||
|
|||
private highChart: any; |
|||
private chart: any; |
|||
private xAxis: Array<any> = []; |
|||
private yAxis: Array<any> = []; |
|||
private series: Array<any> = []; |
|||
private legend: any = null; |
|||
private tooltip: any = null; |
|||
private colorAxis: any = null; |
|||
|
|||
constructor(id: string) { |
|||
this.id = id; |
|||
} |
|||
|
|||
public setChart(type: string = 'heatmap'): void{ |
|||
this.chart = { |
|||
type: 'heatmap', |
|||
marginTop: 40, |
|||
marginBottom: 80, |
|||
plotBorderWidth: 1 |
|||
}; |
|||
} |
|||
|
|||
public setXAxis(text: string, reversed: boolean = true){ |
|||
this.xAxis.push({ |
|||
categories: ['Alexander', 'Marie', 'Maximilian', 'Sophia', 'Lukas', 'Maria', 'Leon', 'Anna', 'Tim', 'Laura'] |
|||
}); |
|||
} |
|||
|
|||
public setYAxis(text: string, max: number, min: number, tickInterval: number, tickAmount: number, opposite: boolean = false, lineWidth: number = 1, reversed: boolean = true){ |
|||
this.yAxis.push({ |
|||
categories: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], |
|||
title: null |
|||
}); |
|||
} |
|||
|
|||
public setColorAxis(): void { |
|||
this.colorAxis = { |
|||
min: 0, |
|||
minColor: '#FFFFFF', |
|||
maxColor: Highcharts.getOptions().colors[0] |
|||
} |
|||
} |
|||
|
|||
public setLegend(enabled: boolean = false){ |
|||
this.legend = { |
|||
align: 'right', |
|||
layout: 'vertical', |
|||
margin: 0, |
|||
verticalAlign: 'top', |
|||
y: 25, |
|||
symbolHeight: 280 |
|||
} |
|||
} |
|||
|
|||
public setTooltip(pointFormat: string, headerFormat: string = '<b>{series.name}</b><br/>'){ |
|||
this.tooltip = { |
|||
formatter: function () { |
|||
return '<b>' + this.series.xAxis.categories[this.point.x] + '</b> sold <br><b>' + |
|||
this.point.value + '</b> items on <br><b>' + this.series.yAxis.categories[this.point.y] + '</b>'; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// public setSeries(name: string)
|
|||
public setSeries(data: any){ |
|||
// let series: any = {
|
|||
// name: name,
|
|||
// data: [],
|
|||
// }
|
|||
// if (color == null || valueSuffix == null){
|
|||
// this.series.push(series);
|
|||
// return
|
|||
// }
|
|||
|
|||
// series.yAxis = yAxis;
|
|||
// series.tooltip = {
|
|||
// valueSuffix: valueSuffix
|
|||
// };
|
|||
// series.color = color;
|
|||
this.series.push({ |
|||
name: 'Sales per employee', |
|||
borderWidth: 1, |
|||
data: data, |
|||
dataLabels: { |
|||
enabled: true, |
|||
color: '#000000' |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public init(): void{ |
|||
const options = this.getOptions(); |
|||
this.highChart = Highcharts.chart(this.id, options); |
|||
// return this.highChart;
|
|||
} |
|||
|
|||
private getOptions(){ |
|||
let options: any = { |
|||
credits: { |
|||
enabled: false |
|||
}, |
|||
chart: this.chart, |
|||
title: { |
|||
text: null |
|||
}, |
|||
xAxis: this.xAxis, |
|||
yAxis: this.yAxis, |
|||
colorAxis: this.colorAxis, |
|||
tooltip: this.tooltip, |
|||
series: this.series |
|||
}; |
|||
if (this.legend != null) |
|||
options.legend = this.legend; |
|||
|
|||
return options; |
|||
} |
|||
|
|||
public updateSeries(index: number, data: Array<any>): void{ |
|||
this.highChart.series[index].setData(data); |
|||
} |
|||
|
|||
public updateXAxis(categories: Array<any>, index: number = 0){ |
|||
this.highChart.xAxis[index].setCategories(categories); |
|||
} |
|||
} |
|||
|
@ -0,0 +1,27 @@ |
|||
const arrayProtoType = Object.create(Array.prototype) |
|||
// 创建一个新的原型,这就是改造之后的数组原型
|
|||
const ArrayProto = [] |
|||
// 重新构建Array原型里面的虽有方法
|
|||
Object.getOwnPropertyNames(Array.prototype).forEach(key => { |
|||
// console.log(key)
|
|||
if (typeof arrayProtoType[key] === "function"){ |
|||
// console.log(arrayProtoType[key], key)
|
|||
ArrayProto[key] = function () { |
|||
// listenMethods(key);
|
|||
return arrayProtoType[key].apply(this, arguments); |
|||
} |
|||
} |
|||
if (key === "push"){ |
|||
listenProps("push") |
|||
} |
|||
ArrayProto[key] = arrayProtoType[key]; |
|||
}) |
|||
|
|||
function listenProps(prop){ |
|||
console.log( prop + 'prop触发了'); |
|||
// 其他你需要监听的逻辑
|
|||
} |
|||
|
|||
export { |
|||
ArrayProto |
|||
} |
Loading…
Reference in new issue