首页 教程 Web前端 微信小程序中使用canvas绘制天气折线图(附代码)

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

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

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

折线

效果图:

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

自定义组件 line-chart

<canvas type="2d" id="line" class="line-class" style="width:{{width}}px;height:{{height}}px" />

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

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

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

.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 需要定位一下

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

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

三阶贝塞尔曲线

效果图

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

emmm,好像并不怎么圆滑

计算控制点

首先写一个点类

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

Canvas贝塞尔曲线绘制工具 (karlew.com)

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

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

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

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

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

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

浓缩一下就是

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

这里的 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

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

至于最后一个点,不需要做任何事,因为 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(); },

评论(0)条

提示:请勿发布广告垃圾评论,否则封号处理!!

    猜你喜欢
    【MySQL】用户管理

    【MySQL】用户管理

     服务器/数据库  2个月前  2.15k

    我们推荐使用普通用户对数据的访问。而root作为管理员可以对普通用户对应的权限进行设置和管理。如给张三和李四这样的普通用户权限设定后。就只能操作给你权限的库了。

    Cursor Rules 让开发效率变成10倍速

    Cursor Rules 让开发效率变成10倍速

     服务器/数据库  2个月前  1.21k

    在AI与编程的交汇点上,awesome-cursorrules项目犹如一座灯塔,指引着开发者们驶向更高效、更智能的编程未来。无论你是经验丰富的老手,还是刚入行的新人,这个项目都能为你的编程之旅增添一抹亮色。这些规则文件就像是你私人定制的AI助手,能够根据你的项目需求和个人偏好,精确地调教AI的行为。突然间,你会发现AI不仅能理解Next.js的最佳实践,还能自动应用TypeScript的类型检查,甚至主动提供Tailwind CSS的类名建议。探索新的应用场景,推动AI辅助编程的边界。

    探索Django 5: 从零开始,打造你的第一个Web应用

    探索Django 5: 从零开始,打造你的第一个Web应用

     服务器/数据库  2个月前  1.12k

    Django 是一个开放源代码的 Web 应用程序框架,由 Python 写成。它遵循 MVT(Model-View-Template)的设计模式,旨在帮助开发者高效地构建复杂且功能丰富的 Web 应用程序。随着每个版本的升级,Django 不断演变,提供更多功能和改进,让开发变得更加便捷。《Django 5 Web应用开发实战》集Django架站基础、项目实践、开发经验于一体,是一本从零基础到精通Django Web企业级开发技术的实战指南《Django 5 Web应用开发实战》内容以。

    MySQL 的mysql_secure_installation安全脚本执行过程介绍

    MySQL 的mysql_secure_installation安全脚本执行过程介绍

     服务器/数据库  2个月前  1.08k

    mysql_secure_installation 是 MySQL 提供的一个安全脚本,用于提高数据库服务器的安全性

    【MySQL基础篇】概述及SQL指令:DDL及DML

    【MySQL基础篇】概述及SQL指令:DDL及DML

     服务器/数据库  2个月前  482

    数据库是长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。数据库不仅仅是数据的简单堆积,而是遵循一定的规则和模式进行组织和管理的。数据库中的数据可以包括文本、数字、图像、音频等各种类型的信息。

    Redis中的哨兵(Sentinel)

    Redis中的哨兵(Sentinel)

     服务器/数据库  2个月前  308

    ​ 上篇文章我们讲述了Redis中的主从复制(Redis分布式系统中的主从复制-CSDN博客),本篇文章针对主从复制中的问题引出Redis中的哨兵,希望本篇文章会对你有所帮助。