小程序中canvas绘图

标签: 小程序中使用canvas绘图  canvas绘制柱状图  canvas绘制多边形雷达图  wepy canvas  canvas echarts

  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. 详情如下:代码全贴

这里写图片描述

<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>

这里写图片描述

<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>
原文链接:加载失败,请重新获取