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

news/2024/7/20 2:31:31 标签: 小程序, canvas

效果图

图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);
      }
    }
  }
})

http://www.niftyadmin.cn/n/908610.html

相关文章

LInux下设置账号有效时间 以及 修改用户名(同时修改用户组名和家目录)

在linux系统中&#xff0c;默认创建的用户的有效期限都是永久的&#xff0c;但有时候&#xff0c;我们需要对某些用户的有效期限做个限定&#xff01;比如&#xff1a;公司给客户开的ftp账号&#xff0c;用于客户下载新闻稿件的。这个账号是有时间限制的&#xff0c;因为是付费…

SQL一次更新多条数据

UPDATE 表名称 SET 列名称 新值 WHERE 列名称 某值 例&#xff1a;想将 serviceId36的number变为20 serviceId37的number变为30 serviceId38的number变为40 该怎么做&#xff1f; 切记不要循环sql语句&#xff0c;拼一条sql语句就解决。 有两种方法&#xff1a; 方法一&am…

html转word 页头页脚代码示例

文章目录原版word代码使用的插件是jQuery-WordExport.js来加页头页脚原版word代码 方便理解 <html xmlns:v"urn:schemas-microsoft-com:vml" xmlns:o"urn:schemas-microsoft-com:office:office" xmlns:w"urn:schemas-microsoft-com:office:word&qu…

Microsoft Visual Stduio 2005 Ent安装报错解决方法

错误:Microsoft Visual Studio 2015 Devenv : 安装时发生严重错误 安装过程第一次出现该错误时&#xff0c;查看了日志文件&#xff0c;错误提示如下&#xff1a; [0EEC:0EF0][2016-10-27T09:37:26]i000: MUX: ExecuteError: Package (vs_devenv) failed: Error Message Id: 14…

7 个超实用的 MySQL 语句写法,让同事们眼前一亮!

在写SQL时&#xff0c;经常灵活运用一些SQL语句编写的技巧&#xff0c;可以大大简化程序逻辑。减少程序与数据库的交互次数&#xff0c;有利于数据库高可用性&#xff0c;同时也能显得你的SQL很牛B&#xff0c;让同事们眼前一亮。 1.插入或替换 如果我们想插入一条新记录&…

WebView 调试

在代码中加入: /** * 设置 webview 是否可调试 */public void setDebugEnabled(Context context) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { if (0 ! (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)) { …

es6...扩展操作符

数组合并 //es5 let book1 [平凡的世界第一部, 平凡的世界第二部, 平凡的世界第三部] let book2 [人生] let book3 book1.concat(book2); //console.log(book3) // (4) ["平凡的世界第一部", "平凡的世界第二部", "平凡的世界第三部", "…

JS之放大镜效果

JS之放大镜效果 哈喽小伙伴们&#xff0c;今天给大家分享的是我制作的一个简易放大镜效果案例&#xff0c;对&#xff0c;没错&#xff0c;就是仿照的电商网站的商品放大镜展示效果&#xff0c;下面我们来看具体的代码&#xff1a; <!DOCTYPE html> <html> <hea…