前言
近期,接了个项目,三端(小程序、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
画布画心电图,但是,没有达到我想要的效果,所以就研究了好几天,自己画了这么一套,要是觉得本文能帮助你的话,还望收藏下来,以便后续使用。