canvas绘制南丁格尔玫瑰图
创建绘图对象
创建一个绘图对象用于存储必要信息和管理绘图
/**
* 初始化图形
* @param {string} id canvas的id
* @param {Array} data 数据
*/
function PieChart(id, data) {
const canvas = document.getElementById(id)
const context = canvas.getContext('2d')
// 赋值属性
this._canvas = canvas
this._context = context
// 定义错误信息,在出现错误信息的时候就直接清空画布(响应式的)
let error = ""
Object.defineProperty(this, '_error', {
enumerable: false,
configurable: false,
set(val) {
if (typeof val === 'string' && val !== "") {
console.error(val)
error = val
if(this._clear) this._clear()
} else {
error = ""
}
},
get() {
return error
}
})
this._maxValue = 0
this._minR = 0 // 中间白色圆圈半径
this._maxR = 0 // 最大半径
this._cache = [] // 离屏canvas图像存储
this.data = this._mapData(data) //数据
this._last = null // 记录上一个选中的区域
_bindHover(this) // 绑定交互事件
}
数据准备
在该系列的前几篇文章绘制了折线图和柱状图,其实很容易能够总结出,在canvas的绘制过程中,一个重点就是位置坐标的计算。此次我们绘制图形的数据为:
let data = [
{lable: 'A', value: 10},
{lable: 'B', value: 5},
{lable: 'C', value: 20},
{lable: 'D', value: 40},
{lable: 'E', value: 30},
]
为了方便后续绘制,所以我们要先对数据进行格式化。
/**
* 获取扇形区域的两个顶点
*/
PieChart.prototype._mapData = function(data) {
if (this._error) return data
let chartZone = this._getChartZone()
if (chartZone == 0) return []
let center = (chartZone[0] + chartZone[1]) / 2 //获取图形中心
let _r = (chartZone[1] - chartZone[0]) / 2 * 0.8 //参考半径值,用来计算半径
this._minR = _r / 4 > 20 ? 20 : _r / 4
this._maxR = _r
let count = 0
let angle = 0
data.forEach((item) => {
if (item.value <=0 ) this._error = '数据value值应当为大于0的值'
if (item.value > this._maxValue) this._maxValue = item.value
count += item.value
})
let _R = Math.sqrt((this._maxR**2 - this._minR**2) * count / this._maxValue + this._minR**2)//参考值
data = data.map((item, index) => { //格式化数据
item.percent = item.value / count //所占比例
item.startAngle = angle //起始角度
item.angle = 2 * Math.PI * item.percent //占的角度
angle += 2 * Math.PI * item.percent
item.color = _getColor(159, 100, 200, index) // 计算一个颜色
item.R = Math.sqrt((_R**2 - this._minR**2) * item.percent + this._minR**2) // 计算半径
return item
})
return data
}
懒得想颜色怎么搭配,就随手写了个颜色计算的小函数,颜色的计算依据初始rgb值计算,尽量避免重复颜色即可。也可以自己写上颜色,当然,数据项多了,自己给每个写个颜色就有点不现实了。
在半径的计算中,半径之间的关系如下图所示:
图形绘制
处理完数据后,接下来就是图形的绘制
/**
* 绘制南丁格尔玫瑰图
*/
PieChart.prototype.draw = function() {
if (this._error) return this
this._clear()
let chartZone = this._getChartZone() // 获取绘图区域
if (chartZone == 0) return this
let center = (chartZone[0] + chartZone[1]) / 2 // 求绘图中心
this.data.forEach((item, index) => { // 绘制每个扇形
this._context.beginPath()
this._context.moveTo(center, center)
this._context.arc(center, center, item.R, item.startAngle, item.startAngle + item.angle, false)
this._context.closePath()
this._context.fillStyle = item.color.str
this._context.fill()
})
// 绘制中心空白区域
this._context.beginPath()
this._context.arc(center, center, this._minR, 0, 2 * Math.PI, false)
this._context.fillStyle = "#ffffff"
this._context.fill()
return this
}
交互效果
像素操作
交互效果的实现,主要问题是对象的选取判断。在本系列的第三篇文章中提到利用坐标的对比来判断鼠标位置是否落在了对象上,这次我们采用一个新的方式来判断。此处要用到canvas的像素操作。canvas的getImageData
的借口可以创建一个ImageData
对象,其中存储了图片每个点的rgba值,存储方式为一个无符号8位整形数组,每个点的rgba占4个值,依次位r、g、b、a的值,都为0-255。我们可以通过这个数组访问到每个像素的颜色值,因为扇形的颜色是唯一的,所以只要相互对比,就能知道鼠标是否落在对象上了。
关于canvas的像素操作,详细可参考MDN文档
添加交互效果
/**
* 交互
*/
function _bindHover(chart) {
if (!chart._canvas) return
chart._canvas.onmousemove = (e) => {
let offsetX = e.offsetX
let offsetY = e.offsetY
let imageData = chart._context.getImageData(0, 0, chart._canvas.width, chart._canvas.height)
let rgba = {}
let base = (offsetY) * imageData.width * 4 + (offsetX) * 4
//获取鼠标点的颜色
rgba.R = imageData.data[base + 0]
rgba.G = imageData.data[base + 1]
rgba.B = imageData.data[base + 2]
rgba.A = imageData.data[base + 3]
let flag = true
for(let item of chart.data) {
let color = item.color
if (color.R === rgba.R && color.G === rgba.G && color.B === rgba.B) {
//颜色对比,选中对象
flag = false
if (chart._cache.length === 0) {
chart._cache.push(chart._canvas.toDataURL('image/png'))
}
if (chart._cache.length === 0 || chart._last !== item.lable) {
if(chart._last !== item.lable) {
//确保会先清除后再画
chart._last = item.lable
_drawCache(chart, () => {
_drawLable(chart, item) //绘制标签
})
} else {
_drawLable(chart, item)
}
break
}
}
}
if (flag) {
//未选中任何对象
if(chart._cache.length > 0) {
chart._last = null
_drawCache(chart) //绘制离屏存储的图像
}
}
}
}
总结
此次南丁格尔玫瑰图的绘制主要是为再次熟悉canvas的基本绘制流程。在整个绘制过程中,引入了新的点就是canvas的像素操作。在css3中也有了滤镜的操作,可以对图片添加一些滤镜来处理图片,但是css3的滤镜可能在一些低版本的浏览器下存在问题,canvas的像素操作就可以用来很好的做一个兼容。有兴趣可以去了解css3的滤镜,在思考下如果是canvas可以如何来实现这些滤镜功能。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!