微信小程序中使用画布canvas实现动态心电图绘制

大家好,我是雄雄。

在这里插入图片描述

内容先知

    • 前言
    • 效果图
    • 实现代码

前言

近期,接了个项目,三端(小程序、PC、公众号)同步开发,PC端没的问题,以前一直做的就是PC端,但是小程序和公众号之前没有做过,只能通过这个项目,边做边学了。

人家都说小程序用原生的特别难,大部分都用uniapp开发,说是这个方便快捷,还能写app呢,因为我们的项目,需要用到蓝牙采集数据、画布画仪表盘以及心电图等,所以我们不太敢用uniapp开发,硬着头皮开始用原生开发。开发的过程中,也遇到过很多问题,但是好在经过我们团队的不懈努力,问题基本都解决了。

下面我就来分享一下,在小程序中,如何通过canvas实现心电图的绘制,包括背景方格,R波等~

效果图

下面我们先来看看效果图:
在这里插入图片描述
由于电脑显示有误差,所以有的线粗有的线细,在手机上没有任何问题。

这个是按照标准来做的,两个大格1cm,一个大格有5个小格,每个小格有10个数据,绘制的进度为:20ms就传递来5个数,随机绘制在图中。

实现代码

首先在wxml中的代码为:

<!-- 心电图 --><view class="tab-middle-ecg"><view class="tab-middle-line"><canvas type="2d" id="ecgGridCanvas" style="width: 100%;height: 255px;margin:-10px auto 0;"></canvas></view><view class="tab-middle-grid" style="width: 290px;margin: -280px auto 0;overflow: hidden;"><canvas type="2d" id="ecgCanvas" style="width: 100%;height: 260px;margin: 0 auto;display: inline-flex;"></canvas></view></view>

然后就是index.js文件的相关代码了:
找到onReady(),在内部添加代码:

//心电图的背景const queryLine = wx.createSelectorQuery();queryLine.select('#ecgGridCanvas').fields({node: true,size: true}).exec((res) => {//为什么要加25?不知道。const width = 500;const height = 330;const canvas = res[0].node;const ctx = canvas.getContext('2d');//获取设备的像素比const dpr = wx.getSystemInfoSync().pixelRatio;canvas.width = width * dpr;canvas.height = height * dpr;ctx.scale(dpr, dpr)//调用画图的方法this.setEcgLineCharts(res[0]);})//心电图的线const query = wx.createSelectorQuery();query.select('#ecgCanvas').fields({node: true,size: true}).exec((res) => {const canvas = res[0].nodeconst ctx = canvas.getContext('2d')const dpr = wx.getSystemInfoSync().pixelRatiocanvas.width = 290 * dprcanvas.height = 160 * dprctx.scale(dpr, dpr)//this.setEcgCharts(res[0])})

methods中的方法如下:

  /*画图*/setEcgLineCharts(canvas) {//构建画布const options = {//背景颜色backgroundColor: '#FFFFFF',//网格颜色gridColor: ['#34a0fb', '#c7dff5', '#63b3f8'],//网格颜色// gridColor:['#34a0fb','#e0acbd','#ea0808'],//控制的是最下面左下角文字的颜色lineColor: '#070606',//这两个可以整体控制方格的大小h: 325,w: 475,data: [],start: true,bgline: true,ampTime: true};//调用画布的方法heartChart(canvas, options);//加载数据this.getEcgData();//heartChart.prototype.drawFun()},//心电数据getEcgData() {//950个数据,理论上是一整屏幕let data = "2043,2045,2049,2044,2042,2054,2055,2053,2045,2045";//测试数据 100个数据,也就是占用两个大格//let data = "2043,2045,2049";//封装的数据let points = [];//最后传递出去的数据let pointsLast = [];//将数据拆分下来放在这个数组里面data.split(',').forEach((res) => {points.push(res);});//当前取到了第几个数据了let currInedx = 0;//当前是第几组数据let currGroupData = 0;//20毫秒传递一次var timer = setInterval(function () {var count = 1;pointsLast = [];for (let i = currInedx; ; i++) {if (count > 5) {break;}pointsLast.push(points[i]);count++;}//一波五个数,下一波的索引就需要加5currInedx += 5;if (currInedx >= points.length) {//clearInterval(timer);//console.log("currIndex的值是:",currInedx);}//调用划线的方法heartChart.prototype.update({data: pointsLast,//传递的这一组数据开始的第一个数据currentData: pointsLast[0],//当前是第几组currGroupData: currGroupData})//组数加1currGroupData++;if (currGroupData >= points.length / 5) {clearInterval(timer);}}, 20);},

需要注意的是:data 变量中的数据应该是有950个,在手机上正好是一个屏幕的数据。

最后一步,在ecg.js文件中,写如下代码:

//画布的属性
let chartOption = null;
//页面dom元素
let domCavas = null;
//Canvas的属性
let ctx = null;
//重新封装的画布属性
let options = null;
//是否是第一次进来
let isFrist = false;
//上一波最后一个y轴的数
let lastY = 0;let heartChart = function (dom, option) {//拿到传递过来的画布的属性,进行构造chartOption = Object.assign({}, option);ctx = dom.node.getContext('2d');domCavas = dom;domCavas.width = chartOption.w;domCavas.height = chartOption.h;//绘制背景方格heartChart.prototype.drawBackground();//左下角文字heartChart.prototype.drawTxt();//调用更新绘制的方法heartChart.prototype.update(option);//定标符号heartChart.prototype.GongLink();}
/*画方格*/
heartChart.prototype.drawBackground = function () {if (chartOption.bgline) {heartChart.prototype.drawMd()//小格heartChart.prototype.drawLg()//大格}
}
/*小格*/
heartChart.prototype.drawMd = function () {ctx.strokeStyle = chartOption.gridColor[1];ctx.strokeWidth=1;ctx.beginPath();var w = chartOption.w, h = chartOption.h;for (var x = 0.5; x < w; x += 5) {ctx.moveTo(x, 0);ctx.lineTo(x, h);ctx.stroke();}for (var y = 0.5; y < h; y +=  5) {ctx.moveTo(0, y);ctx.lineTo(w, y);ctx.stroke();}ctx.closePath();return;
}
/*大格*/
heartChart.prototype.drawLg = function () {ctx.strokeStyle = chartOption.gridColor[2];ctx.strokeWidth = 1;ctx.beginPath();let w = chartOption.w + 0.5, h = chartOption.h + 0.5;let hl = 0;let wl = 0;for (let x = 0.5; x < w; x += 25) {ctx.moveTo(x, 0);ctx.lineTo(x, h);ctx.stroke();wl = x;}for (let y = 0.5; y < h; y += 25) {ctx.moveTo(0, y);ctx.lineTo(w, y);ctx.stroke();hl = y;}ctx.moveTo(wl + 25, 0);ctx.lineTo(wl + 25, hl + 25);ctx.lineTo(0, hl + 25);ctx.stroke();ctx.closePath();
}
/*左下角文字*/
heartChart.prototype.drawTxt = function () {let color = chartOption.lineColor;if (chartOption.ampTime) {ctx.font = '10px Arial';ctx.fontWeight = '300';ctx.fillStyle = color;ctx.fillText("Amp: 10mm/mv  Time: 25mm/sec", 10, 320);}
}
/*定标符号*/
heartChart.prototype.GongLink = function () {ctx.moveTo(0, 175);ctx.lineTo(0, 125);//绘制已定义的路径ctx.stroke();//创建从当前点回到起始点的路径。ctx.closePath();
}
/*** 画线的方法* 1.一共有19个大格子* 2.一个大格子有5个小格子,一共就是19*5=95个小格子* 3.一个小格子放10个数据,一页就是95*10=950个数据* 4.拿过来的数据,需要拆分一下,950个一波,再950个一波,不能都一起画完* 5.先按照逗号截取,截取出来了之后重新赋值到变量里面*/
heartChart.prototype.update = function (option) {//起始一条路径,或重置当前路径。ctx.beginPath();//设置或返回用于笔触的颜色、渐变或模式ctx.strokeStyle = chartOption.lineColor;ctx.lineWidth = '1';//合并对象options = Object.assign({}, chartOption, option);chartOption = options;let point = [];//拿到传递过来的数据point = chartOption.data;//拿到传递的这一组数据开始的第一个数据let currentX = chartOption.currentData;//拿到组数(相当于x轴的坐标)let currGroupData = parseFloat(chartOption.currGroupData) * 2.5;//第一次进来的时候,处理这些if (!isFrist) {//在给定的矩形内清除指定的像素。ctx.clearRect(0, 0, 0, 0);//	把路径移动到画布中的指定点,不创建线条。ctx.moveTo(currentX, 150);//修改这个值,让变成第二次isFrist = true;}else{//将点移动到上一波最后一个点上ctx.moveTo(currGroupData-0.5, lastY);//console.log("当前坐标",currGroupData-0.5, lastY)}//如果没有传来数据,直接出去,别画了if (point.length <= 0) {return false;}//开始遍历输出数据for (let i = 0; i < point.length; i++) {let obj = {//x轴x: currGroupData + i * 0.5,//y轴y: 175 - parseInt((parseInt(point[i]) - 2048) * 0.32),};//判断心电是否有值if (!isNaN(obj.y)) {//y轴有值的时候在画ctx.lineTo(obj.x, obj.y);//记录上一波最后一个y轴lastY = obj.y;}}//绘制已定义的路径ctx.stroke();//创建从当前点回到起始点的路径。ctx.closePath();// console.log("最后一个y",lastY)
}
export {heartChart};

其实,网上也有类似的canvas画布画心电图,但是,没有达到我想要的效果,所以就研究了好几天,自己画了这么一套,要是觉得本文能帮助你的话,还望收藏下来,以便后续使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/323470.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nssl1163-小x游世界树【树形dp,二次扫描和换根法】

正题 题目大意 一棵树&#xff0c;一条边的权是原本的权值减去出发点的加速。 求一个点使得这个点到所有点路径边权和最小。 解题思路 我们先求出以1为根时的答案 然后用换根法 我们从1转移到2&#xff0c;我们会发现 红色的部分的路径都减去的紫色的路径长度&#xff0c;蓝…

结构体跨函数应用(二)

#include<stdio.h> struct Student { int age;char sex; };int main(void){struct Student st;InputStudent(&st);printf("%d,%c",st.age,st.sex);}void InputStudent(struct Student *pstu) {(*pstu).age88;pstu->sexF;}老是出现警告&#xff0c;排查好…

ASP.NET Core依赖注入解读使用Autofac替代实现

1. 前言关于IoC模式&#xff08;控制反转&#xff09;和DI技术&#xff08;依赖注入&#xff09;&#xff0c;我们已经见过很多的探讨&#xff0c;这里就不再赘述了。比如说必看的Martin Fowler《IoC 容器和 Dependency Injection 模式》&#xff0c;相关资料链接都附于文章末尾…

jmeter如何进行一个简单的测试(超级详细,有图有文字,闭着眼都能成功)

大家好&#xff0c;我是雄雄。 内容先知前言软件获取开始测试1.新建线程组2.创建一个请求3.添加HTTP信息头4.开始测试5.查看请求情况前言 上头问题要服务器的配置&#xff0c;基于我们现在做的项目&#xff0c;需要安排别人去采购服务器&#xff0c;给出的消息是&#xff1a;2…

三年半Java后端面试经历

转载自 三年半Java后端面试经历 经过半年的沉淀&#xff0c;加上对MySQL&#xff0c;redis和分布式这块的补齐&#xff0c;终于开始重拾面试信心&#xff0c;再次出征。 鹅厂 面试职位&#xff1a;go后端开发工程师&#xff0c;接受从Java转语言 都知道鹅厂是cpp的主战场&…

nssl1164-观察【平衡树,LCA】

正题 题目大意 一棵树&#xff0c;开始全是白点&#xff0c;两个操作 将一个节点翻转询问一颗棋子与所有面朝上为黑色的棋子lca最深的那个的编号 解题思路 必备技能&#xff1a;平衡树&#xff08;或set库的使用方法&#xff09;&#xff0c;大量卡常技巧&#xff0c;LCA 我…

开封游(一)

今天去开封玩了一下&#xff0c;白天在路程上耽搁太多时间&#xff0c;主要在晚上玩的。终于到显示我米9夜景功能的时候了。 去了包公祠&#xff0c;西司夜市&#xff0c;鼓楼夜市。 下午先去了包公祠 下面是夜景了&#xff0c;特别的炫酷。 首先是西司夜市 进去的时候 这个…

C#中的两把双刃剑:抽象类和接口

问题出现&#xff1a;我们在使用C#的抽象类和接口的时候&#xff0c;往往会遇到以下类似的问题&#xff0c;大致归纳如下:(1)抽象类和接口有什么本质的区别和联系&#xff1f;(2)什么时候选择使用抽象类&#xff0c;然啥时候使用接口最恰当呢&#xff1f;(3)在项目中怎样使用才…

开封游(二)

短暂的永远浪漫&#xff0c;漫长换来不满。人就是这样&#xff0c;第一天是怀着期盼喜悦的心情来的&#xff0c;等到二天&#xff0c;被漫长的行程磨平心境后就再也不想出来旅游了。 总之第二天的心情只能用疲倦来概括。 本来愉悦的心情&#xff0c;而转折点是在吃完海底捞之…

java 高并发面试题

转载自 java 高并发面试题 1、线程与进程 进程是一个实体。每一个进程都有它自己的地址空间&#xff0c;一般情况下&#xff0c;包括文本区域&#xff08;text region&#xff09;、数据区域&#xff08;data region&#xff09;和堆栈&#xff08;stack region&#xff09;。…

ssl提高组周六模拟赛【2018.9.23】

前言 我ssl模拟式爆炸了&#xff0c;我已经打算从1楼跳下去了 成绩 因为比赛的时候数据出bug&#xff0c;所以排名有些奇怪&#xff0c;就不放了。 正题 T1:nssl1162−T1:nssl1162-T1:nssl1162−农夫约的假期【中位数,,,贪心】 博客链接:https://blog.csdn.net/Mr_wuyongcon…

.NetCore之下载文件

本篇将和大家分享的丝.NetCore下载文件&#xff0c;常见的下载有两种&#xff1a;A标签直接指向下载文件地址和post或get请求后台输出文件流的方式&#xff0c;本篇也将围绕这两种来分享&#xff1b;如果对您有好的帮助&#xff0c;请多多支持。允许站点不识别content-type下载…

jeecg-boot中如何修改自定义主题颜色

大家好&#xff0c;我是雄雄。 前言 今天第一次上老丈人家&#xff0c;哈哈哈&#xff0c;有点猝不及防&#xff0c;本来没准备去&#xff0c;结果正好在小区门口碰到&#xff0c;人家让我去&#xff0c;我就把后备箱里面的东西拿下来带着去的。 聊了几个小时&#xff0c;也还…

开封一游后续

游玩过开封后还是很累的&#xff0c;等几个小时的火车更是相当的累。今天有缘坐火车的时候身边是一姑娘&#xff0c;前面没怎么聊&#xff0c;后面她讲她也是八点多到的&#xff0c;我就问她是不是阜阳的&#xff0c;和我们一个学校吗&#xff1f;就这样聊了起来&#xff0c;讲…

P1156-垃圾陷阱【dp】

正题 评测记录:https://www.luogu.org/recordnew/lists?uid52918&pidP1156 题目大意 有若干个垃圾&#xff0c;在tit_iti​时掉落&#xff0c;可以选择吃掉多活fif_ifi​天&#xff0c;也可以堆hih_ihi​高度&#xff0c;高度到达DDD就可以脱逃&#xff0c;求最短逃脱时…

跨平台应用集成(在ASP.NET Core MVC 应用程序中集成 Microsoft Graph)

1谈一谈.NET 的跨平台终于要写到这一篇了。跨平台的支持可以说是 Office 365 平台在设计伊始就考虑的目标。我在前面的文章已经提到过了&#xff0c;Microsoft Graph 服务针对一些主流的开源平台&#xff08;主要用来做跨平台应用&#xff09;都有支持&#xff0c;例如 python,…

两年 JAVA 程序员的面试总结

转载自 两年 JAVA 程序员的面试总结 前言 工作两年左右&#xff0c;实习一年左右&#xff0c;正式工作一年左右&#xff0c;其实挺尴尬的&#xff0c;高不成低不就。因此在面试许多公司&#xff0c;找到了目前最适合自己的公司之后。于是做一个关于面试的总结。希望能够给那…

用指针完成函数参数的调用

#include<stdio.h>addUp(int a,int b,int c){ cab;printf("%d\n",c); return c;}main(){int c;addUp(6,5,c);printf("%d",c); }可以在函数中完成数字的加减但是发现无法再主函数中调用&#xff0c;是个初始值22&#xff0c;所有得想个办法将函数的值…

P1220-关路灯【区间dp】

正题 评测记录:https://www.luogu.org/recordnew/lists?uid52918&pidP1220 题目大意 有n盏灯&#xff0c;每个灯的所在位置和1s消耗的能量不同&#xff0c;现在一个人在c号灯下&#xff0c;他行走速度1m/s&#xff0c;他走到的地方灯会熄灭&#xff0c;求最少消耗能量。…

Java 8中Stream API的这些奇技淫巧!你都Get到了吗?

转载自 Java 8中Stream API的这些奇技淫巧&#xff01;你都Get到了吗&#xff1f; Stream简介 Java 8引入了全新的Stream API。这里的Stream和I/O流不同&#xff0c;它更像具有Iterable的集合类&#xff0c;但行为和集合类又有所不同。stream是对集合对象功能的增强&#x…