Home  >  Article  >  WeChat Applet  >  Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code)

Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code)

青灯夜游
青灯夜游forward
2022-02-11 19:59:167960browse

How to draw a weather line chart in WeChat applet? The following article will introduce to you how to use canvas to draw a weather line chart in the WeChat applet, and use a third-order Bezier curve to fit the temperature points to make them smooth and have a background color at the bottom of the curve. I hope Helpful for everyone!

Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code)

Polyline

Rendering:

Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code)

##Custom component line-chart

<canvas type="2d" id="line" class="line-class"   style="max-width:90%" />
Component({
  externalClasses: [&#39;line-class&#39;],
  properties: {
    width: String,
    height: String,
    data: Array,
  },
  observers: {
    width() {
      // 这里监听 width 变化重绘 canvas
      // 动态传入 width 好像只能这样了..
      const query = this.createSelectorQuery();
      query
        .select(&#39;#line&#39;)
        .fields({ node: true, size: true })
        .exec(res => {
          const canvas = res[0].node;
          const ctx = canvas.getContext(&#39;2d&#39;);
          const width = res[0].width; // 画布宽度
          const height = res[0].height; // 画布高度

          console.log(`宽度: ${width}, 高度: ${height}`);

          const dpr = wx.getSystemInfoSync().pixelRatio;
          canvas.width = width * dpr;
          canvas.height = height * dpr;
          ctx.scale(dpr, dpr);

          // 开始绘图
          this.drawLine(ctx, width, height, this.data.data);
        });
    },
  },
  methods: {
    drawLine(ctx, width, height, data) {
      const Max = Math.max(...data);
      const Min = Math.min(...data);

      // 把 canvas 的宽度, 高度按一定规则平分
      const startX = width / (data.length * 2), // 起始点的横坐标 X
        baseY = height * 0.9, // 基线纵坐标 Y
        diffX = width / data.length,
        diffY = (height * 0.7) / (Max - Min); // 高度预留 0.2 写温度

      ctx.beginPath();
      ctx.textAlign = &#39;center&#39;;
      ctx.font = &#39;13px Microsoft YaHei&#39;;
      ctx.lineWidth = 2;
      ctx.strokeStyle = &#39;#ABDCFF&#39;;

      // 画折线图的线
      data.forEach((item, index) => {
        const x = startX + diffX * index,
          y = baseY - (item - Min) * diffY;

        ctx.fillText(`${item}°`, x, y - 10);
        ctx.lineTo(x, y);
      });
      ctx.stroke();

      // 画折线图背景
      ctx.lineTo(startX + (data.length - 1) * diffX, baseY); // 基线终点
      ctx.lineTo(startX, baseY); // 基线起点
      const lingrad = ctx.createLinearGradient(0, 0, 0, height * 0.7);
      lingrad.addColorStop(0, &#39;rgba(255,255,255,0.9)&#39;);
      lingrad.addColorStop(1, &#39;rgba(171,220,255,0)&#39;);
      ctx.fillStyle = lingrad;
      ctx.fill();

      // 画折线图上的小圆点
      ctx.beginPath();
      data.forEach((item, index) => {
        const x = startX + diffX * index,
          y = baseY - (item - Min) * diffY;

        ctx.moveTo(x, y);
        ctx.arc(x, y, 3, 0, 2 * Math.PI);
      });
      ctx.fillStyle = &#39;#0396FF&#39;;
      ctx.fill();
    },
  },
});

data is the temperature array, such as [1, 2, ...]

Because we don’t know how many temperature values ​​there are , so the width here is dynamically passed in

There is a small problem, that is, if the width is too large, the real machine will not display it...

 // 获取 scroll-view 的总宽度
 wx.createSelectorQuery()
      .select(&#39;.hourly&#39;)
      .boundingClientRect(rect => {
        this.setData({
          scrollWidth: rect.right - rect.left,
        });
      })
      .exec();
<view class="title">小时概述</view>
<scroll-view scroll-x scroll-y class="scroll" show-scrollbar="{{false}}" enhanced="{{true}}">
    <view class="hourly">
      <view wx:for="{{time}}" wx:key="index">{{item}}</view>
    </view>
    <line-chart line-class="line" width="{{scrollWidth}}" height="100" data="{{temp}}" />
</scroll-view>

Write scroll-x and scroll-y here, you need to There will be no problem of absolute positioning offset, and I don’t know why

Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code)

.scroll {
  position: relative;
  height: 150px;
  width: 100%;
}

.hourly {
  display: flex;
  height: 150px;
  position: absolute;
  top: 0;
}

.hourly > view {
  min-width: 3.5em;
  text-align: center;
}

.line { // 折线图绝对定位到底部
  position: absolute;
  bottom: 0;
}

The absolute positioning used here is actually to simulate a line chart like Moji Weather and each day in a block The effect inside, so hourly should be at the same height as scroll-view, and canvas needs to be positioned

Mainly because I don’t know how Moji Weather is implemented, so I can only do this temporarily

Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code)

Third-order Bezier curve

Rendering

Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code)

emmm, it doesn’t seem like much Smooth

Calculate control points

First write a point class

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

Canvas Bezier curve drawing tool (karlew.com)

http://wx.karlew.com/canvas/bezier/

You can know the meaning of each parameter of the third-order Bezier curve through the above website

Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code)

That is, when using bezierCurveTo, the last point is the next point, and the first two are control points

Calculation reference of control points: Bezier curve control points Determined method - Baidu Library

https://wenku.baidu.com/view/c790f8d46bec0975f565e211.html

Condensed it is

Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code)

a and b here can be any positive numbers

So define a method to calculate the control points A and B of a certain point

/**
 * 计算当前点的贝塞尔曲线控制点
 * @param {Point} previousPoint: 前一个点
 * @param {Point} currentPoint: 当前点
 * @param {Point} nextPoint1: 下一个点
 * @param {Point} nextPoint2: 下下个点
 * @param {Number} scale: 系数
 */
calcBezierControlPoints(
  previousPoint,
  currentPoint,
  nextPoint1,
  nextPoint2,
  scale = 0.25
) {
  let x = currentPoint.x + scale * (nextPoint1.x - previousPoint.x);
  let y = currentPoint.y + scale * (nextPoint1.y - previousPoint.y);

  const controlPointA = new Point(x, y); // 控制点 A

  x = nextPoint1.x - scale * (nextPoint2.x - currentPoint.x);
  y = nextPoint1.y - scale * (nextPoint2.y - currentPoint.y);

  const controlPointB = new Point(x, y); // 控制点 B

  return { controlPointA, controlPointB };
}

The scale here is a and b, but the Their values ​​are equal

But the first point does not have previousPoint, and the penultimate point does not have nextPoint2

So when the point is the first, use currentPoint instead of previousPoint

When it is the penultimate point, use nextPoint1 instead of nextPoint2

Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code)

As for the last point, you don’t need to do anything, because the third parameter of bezierCurveTo is the next one Points can be connected by simply providing coordinates, and there is no need to calculate control points

Therefore, the method of drawing a third-order Bezier curve:

/**
 * 绘制贝塞尔曲线
 * ctx.bezierCurveTo(控制点1, 控制点2, 当前点);
 */
drawBezierLine(ctx, data, options) {
  const { startX, diffX, baseY, diffY, Min } = options;

  ctx.beginPath();
  // 先移动到第一个点
  ctx.moveTo(startX, baseY - (data[0] - Min) * diffY);

  data.forEach((e, i) => {
    let curPoint, prePoint, nextPoint1, nextPoint2, x, y;

    // 当前点
    x = startX + diffX * i;
    y = baseY - (e - Min) * diffY;
    curPoint = new Point(x, y);

    // 前一个点
    x = startX + diffX * (i - 1);
    y = baseY - (data[i - 1] - Min) * diffY;
    prePoint = new Point(x, y);

    // 下一个点
    x = startX + diffX * (i + 1);
    y = baseY - (data[i + 1] - Min) * diffY;
    nextPoint1 = new Point(x, y);

    // 下下个点
    x = startX + diffX * (i + 2);
    y = baseY - (data[i + 2] - Min) * diffY;
    nextPoint2 = new Point(x, y);

    if (i === 0) {
      // 如果是第一个点, 则前一个点用当前点代替
      prePoint = curPoint;
    } else if (i === data.length - 2) {
      // 如果是倒数第二个点, 则下下个点用下一个点代替
      nextPoint2 = nextPoint1;
    } else if (i === data.length - 1) {
      // 最后一个点直接退出
      return;
    }

    const { controlPointA, controlPointB } = this.calcBezierControlPoints(
      prePoint,
      curPoint,
      nextPoint1,
      nextPoint2
    );

    ctx.bezierCurveTo(
      controlPointA.x,
      controlPointA.y,
      controlPointB.x,
      controlPointB.y,
      nextPoint1.x,
      nextPoint1.y
    );
  });

  ctx.stroke();
},

[Related learning recommendations:

applet Development Tutorial

The above is the detailed content of Teach you step by step how to use canvas to draw a weather line chart in the WeChat applet (with code). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete