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

简介: 微信小程序中使用画布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].node
        const ctx = canvas.getContext('2d')
        const dpr = wx.getSystemInfoSync().pixelRatio
        canvas.width = 290 * dpr
        canvas.height = 160 * dpr
        ctx.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++;
      }
      //一波五个数,下一波的索引就需要加5
      currInedx += 5;
      if (currInedx >= points.length) {
        //clearInterval(timer);
        //console.log("currIndex的值是:",currInedx);
      }
      //调用划线的方法
      heartChart.prototype.update({
        data: pointsLast,
        //传递的这一组数据开始的第一个数据
        currentData: pointsLast[0],
        //当前是第几组
        currGroupData: currGroupData
      })
      //组数加1
      currGroupData++;
      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画布画心电图,但是,没有达到我想要的效果,所以就研究了好几天,自己画了这么一套,要是觉得本文能帮助你的话,还望收藏下来,以便后续使用。

目录
相关文章
|
8月前
|
前端开发 小程序
小程序使用canvas制作beas64图片
小程序使用canvas制作beas64图片
57 0
|
5月前
|
前端开发 小程序
微信小程序canvas画布绘制base64图片并保存图片到相册中
微信小程序canvas画布绘制base64图片并保存图片到相册中
|
5月前
|
前端开发 小程序
微信小程序绘制canvas时在不同 设备上的大小不同的问题
微信小程序绘制canvas时在不同 设备上的大小不同的问题
104 0
|
5月前
|
前端开发 小程序
微信小程序canvas画布绘制;canvas画布图片保存
微信小程序canvas画布绘制;canvas画布图片保存
42 0
|
8月前
|
编解码 前端开发 小程序
微信小程序canvas画布不清晰解决方法
微信小程序canvas画布不清晰解决方法
|
9月前
|
前端开发 小程序
微信小程序canvas画布绘制base64图片并保存图片到相册中
微信小程序canvas画布绘制base64图片并保存图片到相册中
130 0
|
19天前
|
小程序 前端开发 API
微信小程序全栈开发中的异常处理与日志记录
【4月更文挑战第12天】本文探讨了微信小程序全栈开发中的异常处理和日志记录,强调其对确保应用稳定性和用户体验的重要性。异常处理涵盖前端(网络、页面跳转、用户输入、逻辑异常)和后端(数据库、API、业务逻辑)方面;日志记录则关注关键操作和异常情况的追踪。实践中,前端可利用try-catch处理异常,后端借助日志框架记录异常,同时采用集中式日志管理工具提升分析效率。开发者应注意安全性、性能和团队协作,以优化异常处理与日志记录流程。
|
19天前
|
小程序 安全 数据安全/隐私保护
微信小程序全栈开发中的身份认证与授权机制
【4月更文挑战第12天】本文探讨了微信小程序全栈开发中的身份认证与授权机制。身份认证包括手机号验证、微信登录和第三方登录,而授权机制涉及角色权限控制、ACL和OAuth 2.0。实践中,开发者可利用微信登录获取用户信息,集成第三方登录,以及实施角色和ACL进行权限控制。注意点包括安全性、用户体验和合规性,以保障小程序的安全运行和良好体验。通过这些方法,开发者能有效掌握小程序全栈开发技术。
|
19天前
|
JavaScript 前端开发 小程序
微信小程序全栈开发之性能优化策略
【4月更文挑战第12天】本文探讨了微信小程序全栈开发的性能优化策略,包括前端的资源和渲染优化,如图片压缩、虚拟DOM、代码分割;后端的数据库和API优化,如索引创建、缓存使用、RESTful API设计;以及服务器的负载均衡和CDN加速。通过这些方法,开发者可提升小程序性能,优化用户体验,增强商业价值。

热门文章

最新文章