微信小程序之图表系列——canvas绘制饼状图,带点击效果

一图胜千言,相信很多开发者都没有绕开过图表制作这个坑,在小程序中也是,当然可以用第三方echart等制图插件来做,但小程序要求代码量最大12M,还得分好几个包,一个echart插件就将近1M,要是只做一张表或几张表实在浪费,况且小程序的加载速度与程序包大小也是息息相关的。因此写这系列文章,目的是降低程序包大小,又能实现需要的图表。
上篇文章微信小程序之图表系列——一步步用canvas实现柱状图制作了柱状图,做完之后不管对制作图表还是canvas都会有一个新的认识,这篇文章做个练习,制作一下饼状图,同时还要再增加一个点击效果,因为毕竟很多图表是需要与用户交互的。
上效果:
在这里插入图片描述
体验路径:
在这里插入图片描述
实现思路:
1、与柱状图一样,本质上还是通过计算参数用canvas画,只不过这次需要用到canvas的arc方法,画圆弧,再构成扇形
2、点击效果就需要监听touch方法,也是通过计算判断点击在哪个扇形区域,从而重新绘制

代码:
js

Page({

  /**
   * 页面的初始数据
   */
  data: {
    canvasInfo: {},
    dataList: [{
      title: "男",
      value: 3,
      background: "#b8f2e6"
    }, {
      title: "女",
      value: 8,
      background: "#ffa69e"
    }, {
      title: "未知",
      value: 9,
      background: "#f7db70"
    }],
    pieInfo: {}
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
    this.messureCanvas()
  },
  messureCanvas() {
    let query = wx.createSelectorQuery().in(this);
    // 然后逐个取出navbar和header的节点信息
    // 选择器的语法与jQuery语法相同
    query.select('#pieCanvas').boundingClientRect();
    // 执行上面所指定的请求,结果会按照顺序存放于一个数组中,在callback的第一个参数中返回
    var that = this
    query.exec((res) => {
      // 分别取出navbar和header的高度 
      console.log(res)
      var canvasInfo = {}
      canvasInfo.width = res[0].width
      canvasInfo.height = res[0].height
      that.setData({
        canvasInfo: canvasInfo
      })
      console.log(canvasInfo)
      that.drawPie(-1)
    })
  },
  drawPie(index) {
    const ctxPie = wx.createCanvasContext("pieCanvas")
    var canvasInfo = this.data.canvasInfo
    var dataList = this.data.dataList
    var pieInfo = this.data.pieInfo
    var pieRadius = (canvasInfo.width - 90) / 4
    pieInfo.pieRadius = pieRadius
    var pieX = 30 + pieRadius
    pieInfo.centerX = pieX
    var pieY = 30 + pieRadius
    pieInfo.centerY = pieY
    var totalValue = 0
    for (var i = 0; i < dataList.length; i++) {
      totalValue = totalValue + dataList[i].value
    }
    var area = []
    for (var i = 0; i < dataList.length; i++) {
      var areaItem = {}
      ctxPie.beginPath()
      var start = 0
      for (var j = 0; j < i; j++) {
        start = start + dataList[j].value
      }
      if (i < dataList.length - 1) {
        if(index==i){
          ctxPie.arc(pieX, pieY, pieRadius+5, start / totalValue * 2 * Math.PI, (start + dataList[i].value) / totalValue * 2 * Math.PI)
        }else{
          ctxPie.arc(pieX, pieY, pieRadius, start / totalValue * 2 * Math.PI, (start + dataList[i].value) / totalValue * 2 * Math.PI)
        }
        areaItem.start = start / totalValue * 2 * Math.PI
        areaItem.end = (start + dataList[i].value) / totalValue * 2 * Math.PI
      } else {
        if(index == i){
          ctxPie.arc(pieX, pieY, pieRadius+5, start / totalValue * 2 * Math.PI, 2 * Math.PI)
        }else{
          ctxPie.arc(pieX, pieY, pieRadius, start / totalValue * 2 * Math.PI, 2 * Math.PI)
        }
        areaItem.start = start / totalValue * 2 * Math.PI
        areaItem.end = 2 * Math.PI
      }
      area.push(areaItem)
      ctxPie.lineTo(pieX, pieY);
      ctxPie.setFillStyle(dataList[i].background);
      ctxPie.fill();
      ctxPie.closePath();


      //绘制标注
      var startX = 2 * pieRadius + 60
      var startY = (30 + pieRadius) - 30 * dataList.length / 2 + i * 30
      ctxPie.setFillStyle(dataList[i].background)
      ctxPie.fillRect(startX, startY, 20, 20)


      ctxPie.setFillStyle('#8a8a8a')
      ctxPie.setFontSize(12)
      ctxPie.fillText(dataList[i].title, startX + 30, startY + 15)
      ctxPie.fillText(dataList[i].value + "", startX + 70, startY + 15)
      ctxPie.fillText(parseInt(dataList[i].value * 100 / totalValue) + "%" + "", startX + 100, startY + 15)

    }
    pieInfo.area = area
    this.data.pieInfo = pieInfo
    console.log(this.data.pieInfo)
    ctxPie.draw()
  },
  touchStart(e) {
    var pieInfo = this.data.pieInfo
    var x = e.touches[0].x
    var y = e.touches[0].y
    if ((Math.pow(x - pieInfo.centerX, 2) + Math.pow(y - pieInfo.centerY, 2)) > Math.pow(pieInfo.pieRadius, 2)) {
      console.log("在圆外,不执行")
      return
    }
    var pointPos = 0
    console.log("在圆内,继续执行")
    var angle = Math.atan((y - pieInfo.centerY) / (x - pieInfo.centerX)) / (Math.PI / 180)
    //判断角度值
    if (x > pieInfo.centerX) {
      if (angle > 0) {
        pointPos = angle / 180 * Math.PI
      } else {
        pointPos = angle / 180 * Math.PI + 2 * Math.PI
      }
    } else {
      if (angle > 0) {
        pointPos = angle / 180 * Math.PI + Math.PI
      } else {
        pointPos = angle / 180 * Math.PI + Math.PI
      }
    }
    var index = 0
    for(var i = 0;i<pieInfo.area.length;i++){
      if(pointPos>pieInfo.area[i].start&&pointPos<pieInfo.area[i].end){
        index = i
      }
    }
    console.log("在第"+index+"个区域")
    this.drawPie(index)
  },
})

wxml

<view class="container">
  <canvas style="width:100%;height:350px;margin-top:20px;" canvas-id="pieCanvas" id="pieCanvas" bindtouchstart="touchStart"></canvas>
</view>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玩烂小程序

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值