最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • canvas之旅系列----(四)

    正文概述 掘金(不想做后端的前端不是好运维)   2021-01-10   554

    canvas绘制南丁格尔玫瑰图

    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值计算,尽量避免重复颜色即可。也可以自己写上颜色,当然,数据项多了,自己给每个写个颜色就有点不现实了。

    在半径的计算中,半径之间的关系如下图所示:

    canvas之旅系列----(四)

    图形绘制

    处理完数据后,接下来就是图形的绘制

    /**
     * 绘制南丁格尔玫瑰图
     */
    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可以如何来实现这些滤镜功能。


    起源地下载网 » canvas之旅系列----(四)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元