手把手教你在微信小程序中使用canvas绘制天气折线图(附代码)

青灯夜游

青灯夜游

2022-02-11

9468人浏览

转载

微信小程序中如何绘制天气折线图?下面本篇文章就来给大家介绍一下在微信小程序中使用canvas绘制天气折线图的方法,以及使用三阶贝塞尔曲线拟合温度点,使之变得圆滑,曲线底部有背景色,希望对大家有所帮助!

折线

效果图:

1.gif

自定义组件 line-chart

<canvas></canvas>
Component({
  externalClasses: ['line-class'],
  properties: {
    width: String,
    height: String,
    data: Array,
  },
  observers: {
    width() {
      // 这里监听 width 变化重绘 canvas
      // 动态传入 width 好像只能这样了..
      const query = this.createSelectorQuery();
      query
        .select('#line')
        .fields({ node: true, size: true })
        .exec(res => {
          const canvas = res[0].node;
          const ctx = canvas.getContext('2d');
          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 = 'center';
      ctx.font = '13px Microsoft YaHei';
      ctx.lineWidth = 2;
      ctx.strokeStyle = '#ABDCFF';

      // 画折线图的线
      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, 'rgba(255,255,255,0.9)');
      lingrad.addColorStop(1, 'rgba(171,220,255,0)');
      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 = '#0396FF';
      ctx.fill();
    },
  },
});

data 就是温度数组,如 [1, 2, ...]

因为不知道温度数值有多少个,因此这里的 width 动态传入

有个小问题,就是宽度过大的话真机不会显示...

 // 获取 scroll-view 的总宽度
 wx.createSelectorQuery()
      .select('.hourly')
      .boundingClientRect(rect => {
        this.setData({
          scrollWidth: rect.right - rect.left,
        });
      })
      .exec();
<view>小时概述</view><scroll-view>
    <view>
      <view>{{item}}</view>
    </view>
    <line-chart></line-chart></scroll-view>

这里写 scroll-x 和 scroll-y,要不会出现绝对定位偏移的问题,也不知道为什么 

2.gif

.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;
}

这里使用绝对定位其实是想模拟墨迹天气这种折线图和每一天在一个块内的效果,所以 hourly 要和 scroll-view 等高,canvas 需要定位一下

主要是不知道墨迹天气怎么实现的,只能暂时这样

3.gif

三阶贝塞尔曲线

效果图

4.gif

emmm,好像并不怎么圆滑 

计算控制点

首先写一个点类

微信小程序签名解密ThinkPHP库
微信小程序签名解密ThinkPHP库

微信小程序签名解密ThinkPHP库

下载
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}
Canvas贝塞尔曲线绘制工具 (karlew.com)http://wx.karlew.com/canvas/bezier/

通过上面这个网站可以知道三阶贝塞尔曲线各个参数的意义

5.gif

也就是使用 bezierCurveTo 的时候最后一个点是下一个点,前两个是控制点

控制点的计算参考: 贝塞尔曲线控制点确定的方法 - 百度文库

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

浓缩一下就是

6.gif

这里的 a 和 b 可以是任意正数

因此定义一个计算某点的控制点 A 和 B 的方法

/**
 * 计算当前点的贝塞尔曲线控制点
 * @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 };
}

这里 scale 就是 a 和 b,不过将它们的取值相等

但是第一个点没有 previousPoint,倒数第二个点没有 nextPoint2

因此当点是第一个的时候,使用 currentPoint 代替 previousPoint

当倒数第二个点的时候,使用 nextPoint1 代替 nextPoint2

7.gif

至于最后一个点,不需要做任何事,因为 bezierCurveTo 第三个参数就是下一个点,只需要提供坐标就能连起来,不需要计算控制点

因此绘制三阶贝塞尔曲线的方法:

/**
 * 绘制贝塞尔曲线
 * 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();
},

【相关学习推荐:小程序开发教程

相关文章

微信app下载
微信app下载

微信是一款手机通信软件,支持通过手机网络发送语音短信、视频、图片和文字。微信可以单聊及群聊,还能根据地理位置找到附近的人,带给大家全新的移动沟通体验,有需要的小伙伴快来保存下载体验吧!

下载

相关标签:

微信小程序

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
html5动画制作有哪些制作方法
html5动画制作有哪些制作方法

html5动画制作方法有使用CSS3动画、使用JavaScript动画库、使用HTML5 Canvas等。想了解更多html5动画制作方法相关内容,可以阅读本专题下面的文章。

2023.10.23

2028

6

canva可画使用技巧汇总
canva可画使用技巧汇总

本专题汇集了PHP中文网精心整理的Canva可画新手使用技巧大全,专为零基础用户打造。无论你是想制作精美海报、PPT还是社交媒体配图,这里都有从注册登录、模板搜索到AI辅助设计、导出分享的全流程保姆级教程。通过深入浅出的图文指南,帮助你快速跨越设计门槛,轻松掌握Canva可画的实用操作,让每个人都能成为设计达人!

2026.05.19

144

21

微信是谁开发的
微信是谁开发的

微信是由张小龙所带领的腾讯广州研发中心产品团队打造开发的,并不是马化腾开发的,而腾讯公司总裁马化腾是在产品策划的邮件中确定这款产品的名称叫做“微信”的。想了解更多微信相关的内容,可阅读本专题下面的相关文章。

2024.11.05

4875

8

http500解决方法
http500解决方法

http500解决方法有检查服务器日志、检查代码错误、检查服务器配置、检查文件和目录权限、检查资源不足、更新软件版本、重启服务器或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2023.11.09

1648

4

http请求415错误怎么解决
http请求415错误怎么解决

解决方法:1、检查请求头中的Content-Type;2、检查请求体中的数据格式;3、使用适当的编码格式;4、使用适当的请求方法;5、检查服务器端的支持情况。更多http请求415错误怎么解决的相关内容,可以阅读下面的文章。

2023.11.14

629

3

HTTP 503错误解决方法
HTTP 503错误解决方法

HTTP 503错误表示服务器暂时无法处理请求。想了解更多http错误代码的相关内容,可以阅读本专题下面的文章。

2024.03.12

5112

8

http与https有哪些区别
http与https有哪些区别

http与https的区别:1、协议安全性;2、连接方式;3、证书管理;4、连接状态;5、端口号;6、资源消耗;7、兼容性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.08.16

4621

6

Go 语言 HTTP 编程与中间件开发教程合集
Go 语言 HTTP 编程与中间件开发教程合集

全面讲解 Go 语言 HTTP 编程的核心知识,涵盖 net/http 标准库的 Server/Handler/HandlerFunc 体系、DefaultServeMux 路由注册与自定义路由器、Request / ResponseWriter 请求响应处理、中间件(Middleware)链式设计模式与常见中间件(日志/CORS/认证/限流)开发、http.Client 的超时配置与连接池管理、Cookie / Session 处理、

2026.05.08

174

31

http与https有哪些区别
http与https有哪些区别

http与https的区别:1、协议安全性;2、连接方式;3、证书管理;4、连接状态;5、端口号;6、资源消耗;7、兼容性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.08.16

4621

6

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
秒哒微信小程序发布
秒哒微信小程序发布

共0课时 | 0人学习

微信小程序开发之API篇
微信小程序开发之API篇

共15课时 | 1.6万人学习