欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

小程序中canvas绘图

程序员文章站 2024-02-11 17:18:46
...
  1. 小程序项目github地址:https://github.com/yangdongMC/Eyepetizer.git
  2. 并非原生小程序写法,使用wepy框架进行开发,但wepy并没有把canvas绘图衔接的很好,还是用到小程序部分api
  3. 例如:template中的标签还是使用小程序提供的,js中的声明还是使用const ctx = wx.createCanvasContext(‘id’),与原生的canvas绘制时api有点语法上的区别,但功能都一样,如很多前面加了set,不支持’#333’,改成’#cdcdcd’
  4. 如果项目中要使用动画,简单可考虑canvas,但是复杂了很好的替代品为gif,虽然gif大,但可以微调减帧,减帧了也大,放服务器上、请求、再显示,完美解决。
  5. 详情如下:代码全贴

小程序中canvas绘图

<style>
.radar-canvas {
  width: 100%;
  height: 300px;
}
</style>
<template>
  <view>
    <view>
      <text>雷达图:</text>
      <canvas class="radar-canvas" canvas-id="radar" disable-scroll="{{false}}"></canvas>
    </view>
    <view>
      <Bar></Bar>
    </view>
  </view>
</template>

<script>
import wepy from 'wepy';
//注意:组件引入时,路径必须放在components文件夹下,不然放在pages下,会找不到
import bar from '../components/bar';
/**
  canvas常用属性
  beginPath 起始一条路径,或重置当前路径
  strokeStyle 设置或返回颜色、渐变或模式
  lineTo 创建到达位置x,y的一条线
  colsePath 创建从当前点回到起始点的路径
  stroke 绘制已定义的路径
  fillStyle 填充绘制的颜色
  fill 填充当前绘图(颜色)
 */

//获取媒介宽度
const windowW = wx.getSystemInfoSync().windowWidth;
const centerPointX = windowW / 2;
const centerPointY = centerPointX;
const offset = 3.3;
export default class Radar extends wepy.page {
  config = {
    navigationBarTitleText: 'charts'
  };

  components = {
    Bar: bar
  };
  data = {
    //建立雷达图需要的数据
    radarData: [
      { desc: 'React', value: '0.6' },
      { desc: 'Angular', value: '0.5' },
      { desc: 'Vue', value: '0.8' },
      { desc: 'Wepy', value: '0.5' },
      { desc: 'Canvas', value: '0.3' }
    ],
    //定义半径,减去的部分为文字留的空位
    radius: centerPointX - this.rpx(200)
  };

  onLoad() {}

  //wepy组件,只在page页面中存在的生命周期函数
  onShow() {
    const ctx = wx.createCanvasContext('radar');
    const sideNum = this.radarData.length;
    //定义角度
    const angle = Math.PI * 2 / sideNum;
    this.drawPolygon(ctx, sideNum, angle);
    this.drawRib(ctx, sideNum, angle);
    this.addTags(ctx, this.radarData, sideNum, angle);
    this.addDataPoint(ctx, this.radarData, sideNum, angle);
    this.linePoint(ctx, this.radarData, sideNum, angle);
    ctx.draw();
  }

  //draw polygon 绘制多边形
  drawPolygon(ctx, sideNum, angle) {
    ctx.setStrokeStyle('rgba(83,139,81)');
    //获取单位半径
    const r = this.radius / 5;
    for (let i = 0; i < 5; i++) {
      ctx.beginPath();
      //当前半径
      let currentR = r * (i + 1);
      for (let j = 0; j < sideNum; j++) {
        //Math.PI/3.3是为了设置偏移量,可以自行设置
        const x =
          centerPointX + currentR * Math.cos(angle * j + Math.PI / offset);
        const y =
          centerPointY + currentR * Math.sin(angle * j + Math.PI / offset);
        ctx.lineTo(x, y);
      }
      ctx.setLineDash([2, 2]); //虚线
      ctx.closePath();
      ctx.stroke();
    }
  }

  //line point
  linePoint(ctx, radarData, sideNum, angle) {
    ctx.setStrokeStyle('rgba(83,139,81)');
    ctx.beginPath();
    for (let i = 0; i < sideNum; i++) {
      const x =
        centerPointX +
        this.radius *
          Math.cos(angle * i + Math.PI / offset) *
          radarData[i].value;
      const y =
        centerPointY +
        this.radius *
          Math.sin(angle * i + Math.PI / offset) *
          radarData[i].value;

      ctx.lineTo(x, y);
    }
    ctx.closePath();
    ctx.setFillStyle('rgba(0,91,51,0.2)');
    ctx.fill();
    ctx.stroke();
  }

  //add  dataPoint
  addDataPoint(ctx, radarData, sideNum, angle) {
    for (var i = 0; i < sideNum; i++) {
      var x =
        centerPointX +
        this.radius * Math.cos(angle * i + Math.PI / 3.3) * radarData[i].value;
      var y =
        centerPointY +
        this.radius * Math.sin(angle * i + Math.PI / 3.3) * radarData[i].value;
      ctx.beginPath();
      ctx.arc(x, y, 3, 0, 2 * Math.PI);
      ctx.setFillStyle('rgb(0, 91, 51)');
      ctx.fill();
      ctx.closePath();
    }
  }

  addTags(ctx, radarData, sideNum, angle) {
    ctx.setFontSize(this.rpx(30));
    ctx.setFillStyle('rgb(95, 153, 32)');
    //确定文本位置,可以根据微信小程序文档中的具体方法来设置
    for (var i = 0; i < sideNum; i++) {
      var x = parseInt(
        centerPointX + this.radius * Math.cos(angle * i + Math.PI / 3.3)
      );
      var y = parseInt(
        centerPointY + this.radius * Math.sin(angle * i + Math.PI / 3.3)
      );
      var center = parseInt(centerPointX);
      var centerY = parseInt(centerPointY);
      // console.log('x' + x, 'y' + y, 'center' + center, 'centerY' + centerY);
      if (x < center && y < centerY) {
        ctx.setTextAlign('left');
        ctx.fillText(radarData[i].desc, x - this.rpx(120), y);
      } else if (x - this.rpx(20) > center && y < centerY) {
        ctx.setTextAlign('right');
        ctx.fillText(radarData[i].desc, x + this.rpx(120), y);
      } else if (y > centerY) {
        ctx.setTextAlign('center');
        ctx.fillText(radarData[i].desc, x, y + this.rpx(80));
      } else {
        ctx.setTextAlign('center');
        ctx.fillText(radarData[i].desc, x, y - this.rpx(40));
      }
    }
  }

  //draw rib
  drawRib(ctx, sideNum, angle) {
    ctx.setStrokeStyle('#cdcdcd');
    ctx.beginPath();
    for (var i = 0; i < sideNum; i++) {
      var x = centerPointX + this.radius * Math.cos(angle * i + Math.PI / 3.3);
      var y = centerPointY + this.radius * Math.sin(angle * i + Math.PI / 3.3);
      ctx.moveTo(centerPointX, centerPointY);
      ctx.lineTo(x, y);
    }
    ctx.closePath();
    ctx.stroke();
  }

  //定义公共返回方法
  rpx(num) {
    return Number(Number(windowW / 750 * num).toFixed(2));
  }
}
</script>

小程序中canvas绘图

<style>
.bar-canvas {
  width: 100%;
  height: 500px;
}
</style>

<template>
  <view>
    <text>柱状图</text>
    <view>
      <canvas class="bar-canvas" canvas-id="bar"></canvas>
    </view>
  </view>
</template>

<script>
import wepy from 'wepy';

/**
  可以参考原生绘制https://www.cnblogs.com/linxin/p/6892389.html
 */
//获取媒介的宽
const windowW = wx.getSystemInfoSync().windowWidth;
export default class bar extends wepy.page {
  config = {
    navigationBarTitleText: 'bar'
  };
  onLoad() {
    //这里就不能放在onShow中进行初始化了,因为onShow只渲染当前page页面中存在的生命周期函数,而本组件是被当做子组件显示,所以它自己没有对应的可视化page页面,而是借助父页面显示
    const ctx = wx.createCanvasContext('bar');

    //定义数据源
    const source = [
      { city: '北京', number: 345, color: 'red' },
      { city: '上海', number: 645, color: 'green' },
      { city: '广州', number: 545, color: 'pink' },
      { city: '深圳', number: 945, color: 'purple' }
    ];
    //定义轴线配置
    const x = 50,
      y = 250; //原点坐标
    const xWidth = 30;
    const yWidth = 20;
    //定义矩形配置
    const rectX = 0;
    const rectY = 0;
    const rectWidth = 30;
    const rectHeight = 20;

    //逐步调用绘制方法
    this.darwAxis(ctx, x, y);
    this.darwRect(ctx, source, xWidth, yWidth);
    this.darwScale(ctx, source, yWidth);

    ctx.draw();
  }

  //绘制坐标轴
  darwAxis(ctx, x, y) {
    console.log(ctx);
    ctx.setStrokeStyle('orange');
    ctx.save();
    ctx.translate(x, y);
    ctx.beginPath();

    //X
    ctx.moveTo(0, 0);
    ctx.lineTo(windowW - 90, 0);
    ctx.lineTo(windowW - 90 - 10, -10);
    ctx.moveTo(windowW - 90, 0);
    ctx.lineTo(windowW - 90 - 10, 10);

    //Y
    ctx.moveTo(0, 0);
    ctx.lineTo(0, -20 * 11);
    ctx.lineTo(-10, -20 * 11 + 10);
    ctx.moveTo(0, -20 * 11);
    ctx.lineTo(10, -20 * 11 + 10);

    ctx.stroke();
  }

  //绘制矩形
  darwRect(ctx, source, xWidth, yWidth) {
    ctx.beginPath();
    ctx.setTextAlign('center');
    ctx.setTextBaseline('top');
    ctx.setFontSize('15');
    for (let i = 0; i < source.length; i++) {
      let item = source[i];
      console.log(item);
      ctx.setFillStyle(item.color);
      ctx.fillRect(
        i * 2 * xWidth + xWidth - 20,
        -item.number / 100 * yWidth,
        (windowW - 90) / 9,
        item.number / 100 * yWidth
      );
      ctx.fillText(item.city, -20 + i * 2 * xWidth + xWidth + xWidth / 2, 10);
    }
  }

  //绘制刻度
  darwScale(ctx, source, yWidth) {
    ctx.setTextAlign('end');
    ctx.setTextBaseline('middle');
    for (let i = 0; i <= 10; i++) {
      ctx.moveTo(0, -i * yWidth);
      ctx.lineTo(10, -i * yWidth);
      ctx.fillText(i * 100, -10, -i * yWidth);
    }
    ctx.setStrokeStyle('orange');
    ctx.stroke();
  }
}
</script>