1.前言
var data = [
{value:"10",title:"16-22的年龄人数"},
{value:"15",title:"23-30的年龄人数"},
{value:"25",title:"31-35的年龄人数"},
{value:"10",title:"36及以上的年龄人数"}
]
2.代码
- 实例化传入dom,内部变量初始化
- 调用渲染方法,传入数据
- 接收传入的数据,进行初始化(区间占比,起始角度,结束角度,颜色)
- 渲染扇形模块
- 渲染图例模块
- 渲染延长线和文字模块
<script>
//构造函数
var PieChart = function(dom) {
//初始化
this.init(dom)
}
//初始化方法
PieChart.prototype.init = function(dom){
//初始化dom
var dom = dom || document.querySelector('canvas')
//初始化绘图工具
this.ctx = dom && dom.getContext("2d")
//以下配置暂时写死
//获取画布宽高
this.w = this.ctx.canvas.width
this.h = this.ctx.canvas.height
//饼图的圆心和半径
this.x0 = this.w / 2
this.y0 = this.h / 2
this.r = 150 //半径
//延长线的长度
this.outLineWidth = this.r + 20
//图例大小及间距
this.legend_width = 30
this.legend_height = 16
this.legend_space = 10
//初始化文本对其方式
this.ctx.textAlign = 'left'
//设置字体(设置字体要在计算字体宽度之前)
this.ctx.font = '12px 微软雅黑'
}
//接收数据,执行渲染
PieChart.prototype.render = function(data = []){
//缓存数据
this._data = JSON.parse(JSON.stringify(data))
this.data = JSON.parse(JSON.stringify(data))
//区间信息初始化
this.dataInit()
//绘制扇形
this.drawSector()
//绘制图例
this.drawLegend()
//绘制延长线及说明
this.drawText()
}
//获取随机颜色
PieChart.prototype.getRandomColor = function() {
var r = Math.round(Math.random() * 255)
var g = Math.round(Math.random() * 255)
var b = Math.round(Math.random() * 255)
return 'rgb(' + r + ',' + g + ',' + b + ')'
}
//为区间挂载角度信息
PieChart.prototype.dataInit = function() {
//计算总数
var total = data.reduce((total,item) => {
return total + Number(item.value)
},0)
//遍历前数据
var startRadian = 0 //开始角度
var endRadian = 0 //结束角度
//遍历数据
for(var i=0;i<this.data.length;i++){
//占比
var proportion = this.data[i].value/total
//区间所占角度
var radian = Math.PI * 2 * proportion
//开始角度 = 上一次结束角度
startRadian = endRadian
//结束角度 = 开始角度 + 区间所占角度
endRadian = startRadian + radian
//颜色
var color = this.getRandomColor()
//挂载到数据中
this.data[i].proportion = proportion
this.data[i].radian = radian
this.data[i].startRadian = startRadian
this.data[i].endRadian = endRadian
this.data[i].color = color
}
}
//绘制扇形
PieChart.prototype.drawSector = function(){
//遍历数据
for(var i=0;i<this.data.length;i++){
//当前区间数据
var section_data = this.data[i]
//开启路径
this.ctx.beginPath()
//设置填充颜色
this.ctx.fillStyle = this.data[i].color
//绘制曲线(角度直接读取data)
this.ctx.arc(this.x0, this.y0, this.r, section_data.startRadian, section_data.endRadian)
//延时到原点,以便绘制扇形
this.ctx.lineTo(this.x0, this.y0)
//填充
this.ctx.fill()
}
}
//绘制图例
PieChart.prototype.drawLegend = function(){
//初始化垂直对其方式
this.ctx.textBaseline = 'middle'
//默认一行一个图例,一次往下移动
var item_center_y = -this.legend_height //当前图例y轴坐标
//提示
//遍历数据
for(var i=0;i<this.data.length;i++){
//开启新路径
this.ctx.beginPath()
//设置颜色
this.ctx.fillStyle = this.data[i].color
//标题
var title = this.data[i].title
//当前y轴坐标(依次往下移动)
item_center_y += (this.legend_space + this.legend_height)
//绘制矩形
this.ctx.fillRect(this.legend_space, item_center_y, this.legend_width, this.legend_height)
//绘制文字,文字的基点y坐标为矩形的垂直方向的中心
this.ctx.fillText(title, this.legend_space + this.legend_width + 10, item_center_y + this.legend_height/2)
}
//还原垂直对其方式
this.ctx.textBaseline = 'baseline'
}
//绘制延长线及说明
PieChart.prototype.drawText = function(){
//初始化垂直对其方式
this.ctx.textBaseline = 'bottom'
//遍历数据
for(var i=0;i<this.data.length;i++){
//当前区间数据
var section_data = this.data[i]
//标题内容
var title = section_data.title
//计算中心角度
var middleRadian = (section_data.startRadian + section_data.endRadian) / 2
//延长线坐标(起点坐标为圆心,终点坐标根据正弦 余弦计算)
var outX = this.x0 + this.outLineWidth * Math.cos(middleRadian)
var outY = this.y0 + this.outLineWidth * Math.sin(middleRadian)
//优化文字对齐方式
if (outX >= this.x0) {
//在右边
var underlineX = outX + this.ctx.measureText(title).width
this.ctx.textAlign = 'left'
} else {
//在左边
var underlineX = outX - this.ctx.measureText(title).width
this.ctx.textAlign = 'right'
}
//绘制延长线
//开启路径
this.ctx.beginPath()
//设置颜色
this.ctx.strokeStyle = this.data[i].color
this.ctx.fillStyle = this.data[i].color
//绘制直线
this.ctx.moveTo(this.x0, this.y0)
this.ctx.lineTo(outX, outY)
//绘制下划线
this.ctx.lineTo(underlineX, outY)
this.ctx.stroke()
//绘制文本
this.ctx.beginPath()
this.ctx.fillText(title, outX, outY)
}
//还原垂直对其方式
this.ctx.textBaseline = 'baseline'
}
</script>
3.调用
<script>
var box = document.querySelector("canvas")
var data = [
{value:0,title:"16-22的年龄人数"},
{value:0,title:"23-30的年龄人数"},
{value:0,title:"31-35的年龄人数"},
{value:0,title:"36及以上的年龄人数"}
]
//模拟数据(产生50个数据)
var num = 50
for(var i=0;i<num;i++){
//随机产生16-40的整数
var age = Math.floor(16 + Math.random()*25)
if(age < 23){
data[0].value++
}else if(age < 31){
data[1].value++
}else if(age < 36){
data[2].value++
}else{
data[3].value++
}
}
//创建饼图对象
var pieChart = new PieChart(box)
//传入数据开始绘制饼图
pieChart.render(data)
</script>
4.效果图
5.ES6类语法
<script>
class PieChart{
//构造器
constructor(dom){
//初始化
this.init(dom)
}
//初始化方法
init(dom){
//初始化dom
var dom = dom || document.querySelector('canvas')
//初始化绘图工具
this.ctx = dom && dom.getContext("2d")
//以下配置暂时写死
//获取画布宽高
this.w = this.ctx.canvas.width
this.h = this.ctx.canvas.height
//饼图的圆心和半径
this.x0 = this.w / 2
this.y0 = this.h / 2
this.r = 150 //半径
//延长线的长度
this.outLineWidth = this.r + 20
//图例大小及间距
this.legend_width = 30
this.legend_height = 16
this.legend_space = 10
//初始化文本对其方式
this.ctx.textAlign = 'left'
//设置字体(设置字体要在计算字体宽度之前)
this.ctx.font = '12px 微软雅黑'
}
//接收数据,执行渲染
render(data = []){
//缓存数据
this._data = JSON.parse(JSON.stringify(data))
this.data = JSON.parse(JSON.stringify(data))
//区间信息初始化
this.dataInit()
//绘制扇形
this.drawSector()
//绘制图例
this.drawLegend()
//绘制延长线及说明
this.drawText()
}
//获取随机颜色
getRandomColor() {
var r = Math.round(Math.random() * 255)
var g = Math.round(Math.random() * 255)
var b = Math.round(Math.random() * 255)
return 'rgb(' + r + ',' + g + ',' + b + ')'
}
//为区间挂载角度信息
dataInit() {
//计算总数
var total = data.reduce((total,item) => {
return total + Number(item.value)
},0)
//遍历前数据
var startRadian = 0 //开始角度
var endRadian = 0 //结束角度
//遍历数据
for(var i=0;i<this.data.length;i++){
//占比
var proportion = this.data[i].value/total
//区间所占角度
var radian = Math.PI * 2 * proportion
//开始角度 = 上一次结束角度
startRadian = endRadian
//结束角度 = 开始角度 + 区间所占角度
endRadian = startRadian + radian
//颜色
var color = this.getRandomColor()
//挂载到数据中
this.data[i].proportion = proportion
this.data[i].radian = radian
this.data[i].startRadian = startRadian
this.data[i].endRadian = endRadian
this.data[i].color = color
}
}
//绘制扇形
drawSector(){
//遍历数据
for(var i=0;i<this.data.length;i++){
//当前区间数据
var section_data = this.data[i]
//开启路径
this.ctx.beginPath()
//设置填充颜色
this.ctx.fillStyle = this.data[i].color
//绘制曲线(角度直接读取data)
this.ctx.arc(this.x0, this.y0, this.r, section_data.startRadian, section_data.endRadian)
//延时到原点,以便绘制扇形
this.ctx.lineTo(this.x0, this.y0)
//填充
this.ctx.fill()
}
}
//绘制图例
drawLegend(){
//初始化垂直对其方式
this.ctx.textBaseline = 'middle'
//默认一行一个图例,一次往下移动
var item_center_y = -this.legend_height //当前图例y轴坐标
//提示
//遍历数据
for(var i=0;i<this.data.length;i++){
//开启新路径
this.ctx.beginPath()
//设置颜色
this.ctx.fillStyle = this.data[i].color
//标题
var title = this.data[i].title
//当前y轴坐标(依次往下移动)
item_center_y += (this.legend_space + this.legend_height)
//绘制矩形
this.ctx.fillRect(this.legend_space, item_center_y, this.legend_width, this.legend_height)
//绘制文字,文字的基点y坐标为矩形的垂直方向的中心
this.ctx.fillText(title, this.legend_space + this.legend_width + 10, item_center_y + this.legend_height/2)
}
//还原垂直对其方式
this.ctx.textBaseline = 'baseline'
}
//绘制延长线及说明
drawText(){
//初始化垂直对其方式
this.ctx.textBaseline = 'bottom'
//遍历数据
for(var i=0;i<this.data.length;i++){
//当前区间数据
var section_data = this.data[i]
//标题内容
var title = section_data.title
//计算中心角度
var middleRadian = (section_data.startRadian + section_data.endRadian) / 2
//延长线坐标(起点坐标为圆心,终点坐标根据正弦 余弦计算)
var outX = this.x0 + this.outLineWidth * Math.cos(middleRadian)
var outY = this.y0 + this.outLineWidth * Math.sin(middleRadian)
//优化文字对齐方式
if (outX >= this.x0) {
//在右边
var underlineX = outX + this.ctx.measureText(title).width
this.ctx.textAlign = 'left'
} else {
//在左边
var underlineX = outX - this.ctx.measureText(title).width
this.ctx.textAlign = 'right'
}
//绘制延长线
//开启路径
this.ctx.beginPath()
//设置颜色
this.ctx.strokeStyle = this.data[i].color
this.ctx.fillStyle = this.data[i].color
//绘制直线
this.ctx.moveTo(this.x0, this.y0)
this.ctx.lineTo(outX, outY)
//绘制下划线
this.ctx.lineTo(underlineX, outY)
this.ctx.stroke()
//绘制文本
this.ctx.beginPath()
this.ctx.fillText(title, outX, outY)
}
//还原垂直对其方式
this.ctx.textBaseline = 'baseline'
}
}
</script>