微信小程序-环形图(带动画)

效果图

图2

思路

  1. 使用一个canvas绘制(带动画);
  2. 通过画弧线,设置线宽,来实现圆环效果;
  3. 计算每段圆弧的起始角度和终止角度,用递归做动画;
  4. 绘制完第一段圆弧块–>再绘制下一块,下一块的起始角度是上一块结束的角度+间隙(中间的白色分割线);
  5. 当最后一段圆弧块绘制完成时,绘制延伸线+文字;
  6. 如下图,通过三角函数确定延伸点A点位置,判断延伸点位于中心点的左或右,绘制延伸线;
  7. 确定A点坐标:
    已知A点到圆心的长度edge = 半径r + 伸出去点的长度;
    let edgeX = Math.cos(startAngle + angle / 2) * edge;
    let edgeY = Math.sin(startAngle + angle / 2) * edge;
    let outX = 圆心X + edgeX;
    let outY = 圆心Y + edgeY;
    A点坐标(outX,outY)
    在这里插入图片描述

整体代码

ringCanvas.wxml

<canvas canvas-id="canvas" style="width:{{canvasW}}px;height:{{canvasH}}px;margin:auto;"></canvas>

ringCanvas.js

const screenWidth = 360; //屏幕宽度,自己获取
let pieInitData = { //环形饼图默认初始数据
  mW: 0.9 * screenWidth / 2,
  mH: 0.6 * screenWidth / 2,
  r: 0.15 * screenWidth,
  lineW: 0.07 * screenWidth,
  chink: 2 * Math.PI / 180,/* 环形间距 */
  outSpot: 0.067 * screenWidth, //伸出去点的长度
  outLine: 0.1 * screenWidth, //伸出去线的长度
  signR: 0.008 * screenWidth, //点半径
  fontSize: 0.03 * screenWidth, //字体大小
  textSpace: 0.025 * screenWidth, //文字上下与线的间距
  speed: 2 * Math.PI / 30, /* 速度 */
  moneyColorArr: ['#FF7573', '#7a95ff', '#0F8EE9', '#44d7b6', '#62D174', '#f2d510', '#FEBE3D', '#FFBE9B']
};
Page({
  data:{
    canvasW:0.9*screenWidth,
    canvasH:0.6*screenWidth
  },
  onLoad() {
    let data=[
      { value:'6000-8000/月',ratio:17 },
      { value:'4500-6000/月',ratio:34.8 },
      { value:'3000-4500/月',ratio:36.4 },
      { value:'8000-1w/月',ratio:3.9 },
      { value:'1w-1.5w/月',ratio:5 },
      { value:'2000-3000/月',ratio:2.2 }
    ]
    this.drawPie('canvas',data)
  },
  // 环形饼图
  drawPie(canvasId, data) {
    let ctx = wx.createCanvasContext(canvasId);
    ctx.clearRect(0, 0, pieInitData.mW * 2, pieInitData.mH * 2);
    let oldOutY = 0;
    let oldDir = 'right';
    drawRing(); //绘制圆环
    function drawRing() {
      let all = 0;
      for (let i = 0; i < data.length; i++) {
        all += data[i].ratio
      }
      let angleList = transformAngle();
      let angleArr = [];
      let pieIndex = 0;
      let startAngle = 3 / 2 * Math.PI;
      loop(pieIndex)
      function loop(index) {
        let endAngle = startAngle + angleList[index].angle;
        ctx.beginPath();
        let proportion = 0;
        for (let j = 0; j < index; j++) {
          proportion += data[j].ratio;
        };
        let start = 3 / 2 * Math.PI + 2 * Math.PI * proportion / all;
        let end = start;
        pieAnimate(index, end, start);
        angleArr.push({ startAngle: startAngle, angle: angleList[index].angle })
        startAngle = endAngle;
      }
      /**
       * index 第几个圆弧块
       * end 结束的角度
       * start 开始的角度
       */
      function pieAnimate(index, end, start) {
        setTimeout(() => {
          let endLimit = start + 2 * Math.PI * data[index].ratio / all - pieInitData.chink;
          if (end < endLimit) {
            end += pieInitData.speed;
            if (end > endLimit) {
              end = endLimit
            }
            pieAnimate(index, end, start);
          } else {
            if (pieIndex < data.length - 1) {
              pieIndex++;
              loop(pieIndex)
            } else {
              // 当最后一个圆弧
              angleArr.forEach(function (item, i) {
                drawArcLine(item.startAngle, item.angle, i);//绘制点线
              });
            }
          }
        }, 10)
        ctx.setLineWidth(pieInitData.lineW);
        ctx.setStrokeStyle(pieInitData.moneyColorArr[pieIndex]);
        ctx.arc(pieInitData.mW, pieInitData.mH, pieInitData.r, start, end);
        ctx.stroke();
        ctx.draw(true);
      }
      // 转化弧度
      function transformAngle() {
        let total = 0;
        data.forEach(function (item, i) {
          total += item.ratio;
        });
        data.forEach(function (item, i) {
          var angle = item.ratio / total * Math.PI * 2;
          item.angle = angle;
        });
        return data;
      };
      /**
       * startAngle 圆弧块开始的角度
       * angle 圆弧块扇形的角度
       */
      function drawArcLine(startAngle, angle, index) {
        /*计算点出去的坐标*/
        let edge = pieInitData.r + pieInitData.outSpot;
        let edgeX = Math.cos(startAngle + angle / 2) * edge;
        let edgeY = Math.sin(startAngle + angle / 2) * edge;
        let outX = pieInitData.mW + edgeX;
        let outY = pieInitData.mH + edgeY;
        /*计算线出去的坐标*/
        let edge1 = pieInitData.r + pieInitData.outLine;
        let edgeX1 = Math.cos(startAngle + angle / 2) * edge1;
        let edgeY1 = Math.sin(startAngle + angle / 2) * edge1;
        let outX1 = pieInitData.mW + edgeX1;
        let outY1 = pieInitData.mH + edgeY1;
        ctx.beginPath();
        let dir = 'right';
        if (outX1 > pieInitData.mW) {
          dir = 'right';
        } else {
          dir = 'left';
        }
        ctx.setStrokeStyle(pieInitData.moneyColorArr[index]);
        ctx.setLineWidth(1);
        ctx.setFontSize(pieInitData.fontSize);
        ctx.setTextBaseline('middle');
        if (Math.abs(outY - oldOutY) > 10 || dir != oldDir) { ctx.arc(outX - pieInitData.signR / 2, outY - pieInitData.signR / 2, pieInitData.signR, 0, 2 * Math.PI); }
        ctx.setFillStyle(pieInitData.moneyColorArr[index]);
        ctx.fill();
        ctx.moveTo(outX - pieInitData.signR / 2, outY - pieInitData.signR / 2);
        ctx.lineTo(outX1, outY1);
        /**
         * 优化,
         * 上下距离大于30时,上下显示
         * 上下距离大于10,小于30时,一行显示 3.9%  8000-1w/月 为一行
         * 否则不显示
         */
        if (Math.abs(outY - oldOutY) > 30 || dir != oldDir) {
          oldOutY = outY;
          oldDir = dir;
          if (dir == 'right') {
            /*右*/
            ctx.lineTo(pieInitData.mW * 2, outY1);
            ctx.stroke();
            ctx.setFillStyle('#4a4a4a');
            ctx.setTextAlign('left');
            const rightValueW = ctx.measureText(data[index].value).width;
            const rightRatioW = ctx.measureText(data[index].ratio + '%').width;
            ctx.fillText(data[index].value, pieInitData.mW * 2 - rightValueW, outY1 + pieInitData.textSpace);
            ctx.fillText(data[index].ratio + '%', pieInitData.mW * 2 - rightRatioW, outY1 - pieInitData.textSpace);
          } else {
            /*左*/
            ctx.lineTo(0, outY1);
            ctx.stroke();
            ctx.beginPath();
            ctx.setFillStyle('#4a4a4a');
            ctx.setTextAlign('right');
            const leftValueW = ctx.measureText(data[index].value).width;
            const leftRatioW = ctx.measureText(data[index].ratio + '%').width;
            ctx.fillText(data[index].value, 0 + leftValueW, outY1 + pieInitData.textSpace);
            ctx.fillText(data[index].ratio + '%', 0 + leftRatioW, outY1 - pieInitData.textSpace);
          }
        } else {
          if (Math.abs(outY - oldOutY) >= 10) {
            oldOutY = outY;
            oldDir = dir;
            if (dir == 'right') {
              /*右*/
              const lineOffsetR = ctx.measureText('1000%').width;
              ctx.lineTo(pieInitData.mW * 2 - lineOffsetR, outY1);
              ctx.stroke();
              ctx.setFillStyle('#4a4a4a');
              ctx.setTextAlign('left');
              const rightRatioW = ctx.measureText(data[index].ratio + '% ' + data[index].value + '1000%').width;
              ctx.fillText(data[index].ratio + '% ' + data[index].value, pieInitData.mW * 2 - rightRatioW, outY1 + pieInitData.textSpace);
            } else {
              /*左*/
              const lineOffsetL = ctx.measureText('1000%').width;
              ctx.lineTo(0 + lineOffsetL, outY1);
              ctx.stroke();
              ctx.beginPath();
              ctx.setFillStyle('#4a4a4a');
              ctx.setTextAlign('right');
              const leftRatioW = ctx.measureText(data[index].ratio + '% ' + data[index].value + '1000%').width;
              ctx.fillText(data[index].ratio + '% ' + data[index].value, 0 + leftRatioW, outY1 - pieInitData.textSpace);
            }
          }
        }
        ctx.draw(true);
      }
    }
  }
})
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一个......

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

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

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

打赏作者

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

抵扣说明:

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

余额充值