使用canvas绘制直方图报表

<!DOCTYPE html>
<html>
<head lang="en">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" charset="utf-8"/>
    <title></title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>
<body>
    <div>
		<canvas id="canvas">	
		</canvas>
    </div>
    <script src="./diagram.js"></script>
    <script>
        var diagram = new Diagram();
        diagram.init(
          document.getElementById("canvas"),
          window.screen.width,
          window.screen.height * 2 / 5
        );

        var pm = [41, 12, 23, 21, 45, 78, 67],
          pmTop = [41, 12, 23, 21, 45, 78, 67],
          pmMin = 4,
          companyName = "厦门分公司",
          time = "2014-2-12",
          footer = ["4月23号", 4, 5, 6, 7, 8, 9];
        var list = [
          {
            hArr: pm,
            hArrTop: pmTop,
            title: "排名",
            subTitle: "最高排名:" + pmMin,
            rightTitle: companyName,
            rightSubTitle: time,
            footer: footer
          }
        ];
        diagram.histogram(list);
    </script>  
</body>
</html>

diagram.js文件
/**
 * methods列表:
 * ①init:初始化canvas
 * ②_resolveData:处理数据
 * drawFont:绘制文字
 * drawLine:绘制线条
 * drawBg:绘制背景
 * drawRoundRect:绘制弧形矩形
 * shadow:添加阴影
*/

var Diagram = function(){
	this.canvas = null;
	this.ctx = null;
	this.w = 0;
	this.h = 0;

	this.wWidth = document.body.clientWidth * 2;
	this.wHeight = document.body.clientHeight * 2;

	this.pos = []; //表格起始坐标位置,左下角,即背景图的位置
	this.tableMarginRightAndLeft = 10; //表格外侧距离,距离画布左侧的距离
	this.tableMarginTopAndBottom = 5;	//表格上下距离
	this.tablePaddingRightAndLeft = 20;	//表格内边距
	this.tablePaddingTopAndBottom = 20;
	//计算绘制区域大小,表格内容总宽,不包含外边距
	this.tableWidth = this.wWidth -this.tableMarginRightAndLeft*2; 
	this.tableHeight = this.wHeigh/3 - this.tableMarginTopAndBottom*2;	

	//表格内容总宽,不包含内外边距
	this.tableContentWidth = this.tableWidth-this.tablePaddingRightAndLeft*2; 
	this.tableContentHeight = this.tableHeight-this.tablePaddingTopAndBottom*2;

	this.data = null;//数据

	this.titleScale = 1/4; 	//表格三者占据的比例
	this.areaScale = 1/2;
	this.bottomScale = 1/7;
	
	this.iconType = "A";	//图表样式

}

Diagram.prototype.init = function(canvas,w,h,iconType){
	if(iconType){
		this.iconType = iconType;
	}
	this.canvas = canvas;
	this.ctx = canvas.getContext("2d");
	this.w = w*2;
	this.h = h*2;
	this.canvas.width = w*2;
	this.canvas.height = h*2;
	this.canvas.style.width = w + "px";
	this.canvas.style.height = h + "px";
};
// 绑定事件
Diagram.prototype.bindEvent = function(){

}
//获取数据
Diagram.prototype.fetchData = function(cb){

}
//处理数据 ——不太明白 ×××
Diagram.prototype._resolveData = function(data){ 
	var self = this;
	var num = data.length;//表格个数
	this.tableWidth = this.w - this.tableMarginRightAndLeft*2;	
	this.tableHeight = this.h/num - this.tableMarginTopAndBottom*2;
	this.tableContentWidth = this.tableWidth-this.tablePaddingRightAndLeft*2; 
	this.tableContentHeight = this.tableHeight-this.tablePaddingTopAndBottom*2;

	for(var i=0;i<num;i++){
		var max = Math.max.apply(this, data[i].hArr); // 取最大值
		max = max*1.2+1;
		data[i].hArr = data[i].hArr.map(function(item,index,arr){
			return self.areaScale*item/max*(self.tableContentHeight-self.w/25);
		});
		this.pos.push({
			x:this.tableMarginRightAndLeft,
			y:this.h/num*(i+1)-this.tableMarginTopAndBottom
		});
	}
	this.data = data;
}
//数据获取失败
Diagram.prototype._fetchDataFailed = function(){}

//绘制直方图
Diagram.prototype.histogram = function(data){
	if(!data){return}
	this._resolveData(data);//处理数据
	
	var iconType = this.iconType;
	var histogramStyle = this.histogramStyle;
	
	for(var i = 0;i<data.length;i++){	//绘制图表个数
		//绘制背景
		this.drawBg({
				x : this.pos[i].x,	//背景左下角坐标
				y : this.pos[i].y,
				w : this.tableWidth,	//宽高
				h : this.tableHeight
		},histogramStyle[iconType].bg);
		this.rectMatrix({
				hArr:this.data[i].hArr,	//直方图,柱形高度
				hArrTop:this.data[i].hArrTop,	//直方图,柱形上方显示字体
				pos:{//起始位置
					x:this.pos[i].x,
					y:this.pos[i].y- this.tablePaddingTopAndBottom - this.tableContentHeight*this.bottomScale
				}
			}, histogramStyle[iconType].rectMatrix);
		// //绘制线条,上
		this.drawLine({
				x : this.pos[i].x+this.tablePaddingRightAndLeft, 
				y : this.pos[i].y-this.tableHeight*(1-this.titleScale),
				endX : this.pos[i].x+this.tablePaddingRightAndLeft+this.tableContentWidth,
				endY : this.pos[i].y-this.tableHeight*(1-this.titleScale)
		}, histogramStyle[iconType].lineTop );
		//绘制线条,下
		this.drawLine({
				x : this.pos[i].x+this.tablePaddingRightAndLeft, 
				y : this.pos[i].y-this.tableContentHeight*this.bottomScale-this.tablePaddingTopAndBottom,
				endX :	this.pos[i].x+this.tablePaddingRightAndLeft+this.tableContentWidth,
				endY :	this.pos[i].y-this.tableContentHeight*this.bottomScale-this.tablePaddingTopAndBottom
		},	histogramStyle[iconType].lineBottom);
		//绘制底部菜单
		this.drawBottomBar({
				O:{
					x:this.pos[i].x+this.tablePaddingRightAndLeft,
					y:this.pos[i].y
				},
				tableWidth:this.tableContentWidth,
				footer:data[i].footer
		},histogramStyle[iconType].drawBottomBar);
		//绘制顶部部菜单		
		this.drawHeadBar({
				O:{
					x:this.pos[i].x+this.tablePaddingRightAndLeft,
					y:this.pos[i].y-this.tableHeight*(1-this.titleScale)
				},
				width:this.tableContentWidth,
				height: this.tableContentHeight,
				title:data[i].title || "",
				subTitle:data[i].subTitle || "",
				rightTitle:data[i].rightTitle || "",
				rightSubTitle:data[i].rightSubTitle || ""
		},	histogramStyle[iconType].drawHeadBar);
	}
};
/**
 * 绘制文字 √
 * options :x,y,font,color,textAlign,textBaseline,topFontSizePercent
 * */
Diagram.prototype.drawFont = function(options){
	this.ctx.save();
	this.ctx.fillStyle = options.color || "#fff";
	this.ctx.font = this.w * options.fontSizePercent +"px 宋体";
	this.ctx.textAlign = options.textAlign || "center";
	this.ctx.textBaseline = options.textBaseline ||"bottom";
	this.ctx.fillText(options.font,options.x,options.y);
	this.ctx.restore();
}

// 绘制背景 √
Diagram.prototype.drawBg = function(data,options){
	var x = data.x;
	var y = data.y;
	var w = data.w;
	var h = data.h;
	var bgColor1 = options.bgColor1;
	var bgColor2 = options.bgColor2;
	
	// 保存画笔状态
	this.ctx.save();
	// 线性渐变:(x,y,x1,y1)起点xy坐标,终点xy坐标
	var gradient = this.ctx.createLinearGradient(x+w/2, y, x+w/2, y-h);
	this.ctx.lineWidth="0";
	// 添加阴影的效果欠佳:去除
	// this.shadow(2,2,2,"rgba(255,214,206,0.5)"); 
	gradient.addColorStop(0,bgColor1);
	gradient.addColorStop(1,bgColor2);
	this.ctx.fillStyle = gradient;
	// 绘制弧形矩形
	this.drawRoundRect(x,y-h,w,h,10);

	this.ctx.fill();
	this.ctx.stroke();
}
/**
 * 绘制长方形矩阵 √
 * options:
 * lineWPercent:0.25,	//柱形宽度rectRadiusPercent: 0.5,
 * strokeStyle:"rgba(253,163,99,0.8)",	//柱形颜色
 * topFontColor:"#fff",	//柱形顶部文字颜色 
 * topFontSizePercent:1/25	//柱形顶部文字大小
*/
Diagram.prototype.rectMatrix = function(data,options){
	var ctx = this.ctx;
	
	var hArr = data.hArr;	//直方图,柱形高度
	var hArrTop = data.hArrTop;	//直方图,柱形上方显示字体
	var pos = data.pos;	//起始位置
	
	var lineWPercent = options.lineWPercent;	//柱形宽度
	var rectRadiusPercent = options.rectRadiusPercent;	//柱形圆角大小
	var strokeStyle = options.strokeStyle;	//柱形颜色
	var topFontColor = options.topFontColor;	//柱形顶部文字颜色
	var topFontSizePercent = options.topFontSizePercent;	//柱形顶部文字大小
	var isShadow = options.isShadow;
	
	var num = hArr.length;		//直方图,柱形个数
	
	var w = this.tableContentWidth/(num+num-1);
	var lineW = num == 1 ? w/3 : w*lineWPercent;	//柱形宽度
	var y = data.pos.y;	//柱形底部位置
	
	if(isShadow){
		ctx.save();
		ctx.shadowOffsetX = 5;	//绘制阴影
		ctx.shadowOffsetY = 5;
		ctx.shadowBlur = 4;
		ctx.shadowColor = "rgba(0, 0, 0, 0.2)";
	}
	
	this.ctx.fillStyle =  strokeStyle;		//填充柱形,颜色
	for(var i=0; i<num; i++){	//绘制柱形
		var x = pos.x + this.tablePaddingRightAndLeft + this.tableContentWidth/num*(1/2+i);	
		this.drawRoundRect(x-lineW/2,y-hArr[i],lineW,hArr[i],lineW*rectRadiusPercent);
		this.ctx.fill();
		if(hArrTop){ 
			//options :x,y,font,color,textAlign,textBaseline,fontSizePercent
			this.drawFont({
				x : x,
				y : y-hArr[i]-10,
				font : hArrTop[i],
				color : topFontColor,
				fontSizePercent : topFontSizePercent
			});
		}//绘制文字
	}
	ctx.restore();
}

// 绘制线条 √
Diagram.prototype.drawLine = function(data,options){
	var x = data.x;
	var y = data.y;
	var endX = data.endX;
	var endY = data.endY;
	var offsetY = options.offsetY;
	var color = options.color  || "#ffa164";
	var lineWidth = options.lineWidth || 4 ;
	
	var ctx = this.ctx;
	ctx.save()
	ctx.strokeStyle = color;
	ctx.lineWidth = lineWidth;
	ctx.beginPath();
	ctx.moveTo(x, y + parseFloat(lineWidth) + parseFloat(offsetY) );
	ctx.lineTo(endX, endY + parseFloat(lineWidth) + parseFloat(offsetY) );
	ctx.stroke();
	ctx.closePath();
	ctx.restore();
}

// 绘制头部 √
Diagram.prototype.drawHeadBar = function(data,options){
	console.log(data,options)
	var fontColor = options.fontColor || "#fff";
	var O = data.O;

	var ctx = this.ctx;
	
	ctx.save();
	ctx.translate(O.x,O.y); // 想当与将画布移动到了header的左下角
	ctx.fillStyle = fontColor;
	
	ctx.textAlign = "start";
	ctx.textBaseline = "bottom";
	
	ctx.font = this.w/20+"px 宋体";
	ctx.fillText(data.title,0,-this.titleScale*data.height/2);
	ctx.font = this.w/25+"px 宋体";
	ctx.fillText(data.subTitle,0,-4);
	
	// 观察头部下方的位置
	// ctx.beginPath();
	// ctx.moveTo(0,0);
	// ctx.lineTo(this.tableContentWidth,0); 
	// ctx.stroke();

	ctx.textAlign = "end";
	ctx.font = this.w/22+"px 宋体";
	ctx.fillText(data.rightTitle,data.width,-this.titleScale*data.height/2);
	ctx.font = this.w/25+"px 宋体";
	ctx.fillText(data.rightSubTitle,data.width,-4);
	
	ctx.restore();
}

// 绘制下测样式 √
Diagram.prototype.drawBottomBar = function(data,options){
	var fontColor = options.fontColor || "#fff";
	var fontSizePercent = options.fontSizePercent || 0.04;
	
	this.ctx.fillStyle = fontColor;
	var arr = data.footer;
	this.ctx.font = this.w * fontSizePercent+"px 宋体";
	this.ctx.textAlign = "center";
	this.ctx.textBaseline = "center";
	
	// 观察底部的上方位置
	// this.ctx.beginPath();
	// this.ctx.moveTo(data.O.x,data.O.y-this.tableContentHeight*this.bottomScale/3-this.tablePaddingTopAndBottom);
	// this.ctx.strokeStyle = "green";
	// this.ctx.lineTo(this.tableContentWidth,data.O.y-this.tableContentHeight*this.bottomScale/3-this.tablePaddingTopAndBottom); 
	// this.ctx.stroke();
	
	for(var i=0;i<arr.length;i++){
		// 从中间点绘制,居中
		this.ctx.fillText(arr[i],  data.O.x+data.tableWidth/arr.length*(1/2+i),  data.O.y-this.tableContentHeight*this.bottomScale/3-this.tablePaddingTopAndBottom)
	}
}

// 渲染数据
Diagram.prototype.renderData = function(data){

}

// 添加阴影 √
Diagram.prototype.shadow = function(shadowOffsetX,shadowOffsetY,shadowBlur,shadowColor){
	this.ctx.shadowOffsetX = shadowOffsetX || 5;//阴影
	this.ctx.shadowOffsetY = shadowOffsetY || 5;
	this.ctx.shadowBlur = shadowBlur || 4;
	this.ctx.shadowColor = shadowColor || "rgba(0, 0, 0, 0.2)";
}
// 绘制弧形矩形 √
Diagram.prototype.drawRoundRect = function(x, y, width, height, radius){  
	var ctx = this.ctx; 
    ctx.beginPath();   
    ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2);   
    ctx.lineTo(width - radius + x, y);   
    ctx.arc(width - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2);   
    ctx.lineTo(width + x, height + y - radius);   
    ctx.arc(width - radius + x, height - radius + y, radius, 0, Math.PI * 1 / 2);   
    ctx.lineTo(radius + x, height +y);   
    ctx.arc(radius + x, height - radius + y, radius, Math.PI * 1 / 2, Math.PI);   
    ctx.closePath();   

} 

//直方图样式 √
Diagram.prototype.histogramStyle = {
		A:{
			bg:{
				bgColor1:"#fd4135",	//背景颜色
				bgColor2:"#fe9150"
			},
			rectMatrix:{
				lineWPercent:0.25,	//柱形宽度,百分比
				rectRadiusPercent: 0.5,	//柱形圆角大小
				strokeStyle:"rgba(253,163,99,0.8)",	//柱形颜色
				topFontColor:"#fff",	//柱形顶部文字颜色
				topFontSizePercent:1/25,	//柱形顶部文字大小
				isShadow: true
			},
			lineTop:{
				color: "#ffcd86",		//线颜色
				lineWidth: "2",		//线宽度
				offsetY: "8"	//offsetY在y上偏移
			},
			lineBottom:{
				color :	"#ff7156",
				lineWidth : "4",
				offsetY : "8"
			},
			drawBottomBar:{
				fontColor:"#fff",	//脚部文字颜色
				fontSizePercent: 1/25	//脚部文字大小
			},
			drawHeadBar:{
				fontColor:"#fff"	//顶部文字颜色
			}
		},
		B:{
			bg:{
				bgColor1:"#a3d9e5",	//背景颜色
				bgColor2:"#a3d9e5"
			},
			rectMatrix:{
				lineWPercent:1,	//柱形宽度,百分比
				rectRadiusPercent: 0,	//柱形圆角大小
				strokeStyle:"#f6af3b",	//柱形颜色
				topFontColor:"black",	//柱形顶部文字颜色
				topFontSizePercent:1/25,	//柱形顶部文字大小
				isShadow: false
				
			},
			lineTop:{
				color: "#a3d9e5",		//线颜色
				lineWidth: "0",		//线宽度
				offsetY: "0"		//offsetY在y上偏移
			},
			lineBottom:{
				color :	"black",
				lineWidth : "1",
				offsetY : "1"
			},
			drawBottomBar:{
				fontColor:"black",	//脚部文字颜色
				fontSizePercent: 1/30	//脚部文字大小
			},
			drawHeadBar:{
				fontColor:"black"	//顶部文字颜色
			}
		}
}

//设置diagram样式
Diagram.prototype.set = function(options){
	var histogram = options.histogram;
	var iconType = options.iconType;
	if(iconType){
		this.iconType = iconType;
	}
	if(options.tablePaddingTopAndBottom){	
		this.tablePaddingTopAndBottom = options.tablePaddingTopAndBottom	;
	}
	if(histogram){
		for(item in histogram){
			this[item] = histogram[item];
		}
	}
	
}

效果图

转载于:https://my.oschina.net/shuaihong/blog/3041574

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在JavaScript中使用Canvas绘制YUV420图像是一个相对复杂的任务,因为Canvas默认使用RGB颜色模式而不是YUV。你需要进行一些额外的步骤来将YUV420数据转换为RGB,并将其绘制Canvas上。 下面是一个基本的示例代码,用于展示如何使用Canvas绘制YUV420图像: ```html <!DOCTYPE html> <html> <head> <title>Canvas YUV420</title> </head> <body> <canvas id="myCanvas" width="400" height="300"></canvas> <script> // 获取Canvas元素 var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); // 获取YUV420图像的Y、U、V分量数据(假设已经获取到了) // 假设Y分量数据为yData,U分量数据为uData,V分量数据为vData // 创建一个ImageData对象用于存储RGB数据 var imageData = ctx.createImageData(canvas.width, canvas.height); var data = imageData.data; // YUV420转RGB函数 function yuv420ToRgb(y, u, v) { var r, g, b; // YUV到RGB转换公式 r = y + 1.402 * (v - 128); g = y - 0.344136 * (u - 128) - 0.714136 * (v - 128); b = y + 1.772 * (u - 128); // 将RGB值限制在0到255之间 r = Math.max(0, Math.min(255, r)); g = Math.max(0, Math.min(255, g)); b = Math.max(0, Math.min(255, b)); return [r, g, b]; } // 遍历每个像素 for (var i = 0; i < canvas.width * canvas.height; i++) { var y = yData[i]; var u = uData[Math.floor(i / 4)]; var v = vData[Math.floor(i / 4)]; // 转换YUV到RGB var rgb = yuv420ToRgb(y, u, v); // 填充RGB值到ImageData var dataIndex = i * 4; data[dataIndex] = rgb[0]; // 红色通道 data[dataIndex + 1] = rgb[1]; // 绿色通道 data[dataIndex + 2] = rgb[2]; // 蓝色通道 data[dataIndex + 3] = 255; // 不透明度 } // 将ImageData对象绘制Canvas上 ctx.putImageData(imageData, 0, 0); </script> </body> </html> ``` 在上面的示例中,我们假设已经获取到了YUV420图像的Y、U、V分量数据,并创建了一个ImageData对象来存储RGB数据。然后,我们使用一个yuv420ToRgb函数将YUV数据转换为RGB,并将转换后的RGB值填充到ImageData对象中。最后,我们使用putImageData方法将ImageData对象绘制Canvas上。 请注意,上述示例是一个简化的版本,并未考虑YUV420图像的具体格式和数据排列。在实际应用中,你可能需要根据实际情况进行适当的解析和处理。此外,由于JavaScript是单线程的,处理大量像素数据可能会导致性能问题,你可能需要考虑使用Web Worker或其他优化技术来提高性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值