用canvas画曲线图

news2024/9/24 21:20:59

1.创建 canvas 绘图上下文(指定 canvasId)

定义:在自定义组件下,第二个参数传入组件实例this,以操作组件内 canvas 组件。需要指定 canvasId,该绘图上下文只作用于对应的 canvas。

参数

参数类型说明
canvasIdString画布表示,传入定义在 canvas组件的 canvas-id或id(支付宝小程序是id、其他平台是canvas-id)
componentInstanceObject自定义组件实例 this ,表示在这个自定义组件下查找拥有 canvas-id 的 canvas组件 ,如果省略,则不在任何自定义组件内查找

返回值
CanvasContext

具体可查看文档,由于我自己的项目需要画个振动波型曲线图,所以我的实现方式如下:

2.通过uni.connectSocket,创建一个 WebSocket 连接。(波形图的父页面)

<template>
	<view>
		<!-- 振动波型图组件 -->
		<curve ref="otdrCurve" cid="otdr-curve" class="charts"></curve>
		<!-- 调整距离 -->
		<view class="slider-range">
			<!-- 滑动条插件 -->
			<slider-range
				:bar-height="8"
				:block-size="30"
				:decorationVisible="true"
				:format="format"
				:max="rangMax"
				:min="0"
				:step="1"
				:value="range"
				@change="handleRangeChange"
			/>
		</view>
	</view>
</template>
<script>
import { ACCESS_TOKEN } from '@/common/util/constants'
import { getBaseUrl } from '@/common/service/config.js'
import { http } from '@/common/service/service.js'
import curve from './curve'
// 定义滑动条的最大距离
let maxDistance = 40 * 1000
// 曲线数据(全局变量)
let curveData = {
	start: 0,
	end: maxDistance,
	data: [],
	markLine: []
}
export default {
	components: {
		curve
	},
	data() {
		return {
			start: 0,
			end: 0,
			socketTask:{}rangMax: maxDistance,
			range: [0, maxDistance],
			getData: null, //用于接收防抖函数的返回值
		}
	},
	mounted() {
		// console.log('端口波形 mounted')
		this.$refs.otdrCurve.setStartEnd(this.start, this.end)
		this.drawChart()
		this.connectSocketInit()
		// 接收防抖函数(因为滑动条触发抖动,因此加个防抖函数处理)
		this.getData = this.rangeDebounce(this.changeCalibrateDis, 500)
	},
	// 实例销毁之前调用
	beforeDestroy() {
		// 实例销毁前清空敲击的波形图
		curveData.markLine = []
		this.closeSocket()
	},
	methods: {
		// 进入这个页面的时候创建websocket连接【整个页面随时使用】
		connectSocketInit() {
			let token = uni.getStorageSync(ACCESS_TOKEN)//用户token
			let url = getBaseUrl()
				.replace('https://', 'ws://')
				.replace('http://', 'ws://')
			url = url + '/ifcs/websocket/wave/' + token + '/' + this.task.devicecode
			console.log('url', url)//这里的url需要跟后端商量都要拼接什么标识进行连接
			this.socketTask = uni.connectSocket({//此API返回一个 socketTask 对象
				url: url,//服务器接口地址
				success(data) {
					console.log('websocket连接成功', data)
				}
			})
			this.socketTask.onMessage(this.onMessage)
			this.socketTask.onOpen(() => {
				console.log('WebSocket连接正常打开中...!')
			})
			this.socketTask.onError(e => {
				console.log('WebSocket onError', e)
			})
			this.socketTask.onClose(() => {
				console.log('WebSocket已经被关闭了!')
			})
		},
		//WebSocket 接受到服务器的消息事件的回调函数
		onMessage(e) {
			const data = eval('(' + e.data + ')')//data	String/ArrayBuffer	服务器返回的消息
			if (data.type === 'shakedata' && data.originalDataList) {
				//更新最大长度
				if (data.maxlength && maxDistance !== data.maxlength) {
					maxDistance = data.maxlength
					this.rangMax = data.maxlength
					this.range[1] = data.maxlength
					this.end = data.maxlength
				}
				// 波形
				let array = data.originalDataList
				let length = data.originalDataList.length
				// console.log('波形length: ' + length)
				if (length > 0) {
					const temp = []
					//计算数据抽取力度
					let points = 300 //图表内显示的点数
					let step = 10
					if (length > points) {
						step = length / points
					}
					step = Math.floor(step)
					//曲线波形数据
					for (let i = 0; i < length - step; i += step) {
						temp.push(array[i])
					}
					curveData.data = temp
				}
			}
			this.drawChart()
		},
		// 传入数据到波形图/点时域图组件
		drawChart() {
			this.$refs.otdrCurve.drawChart(curveData)
		},
		// 关闭websocket【离开这个页面的时候执行关闭】
		closeSocket() {
			if (this.socketTask) {
				this.socketTask.close({})
			}
		},
		//滑动条格式
		format(val) {
			return val + '米'
		},
		// 调整距离
		handleRangeChange(e) {
			this.start = e[0]
			this.end = e[1]
			curveData.start = e[0]
			curveData.end = e[1]
			this.getData(e)
		},
		//更改敲击标定距离
		changeCalibrateDis() {
			http.post('/ifcs/calibrate/changeCalibrateDis', {//更改距离后及时调用接口请求最新数据
				taskid: this.task.id,
				devicecode: this.task.devicecode,
				startdistance: this.start,
				enddistance: this.end
			}).then(res => {
				const {startdistance,enddistance} = res.config.data
				curveData.start = startdistance
				curveData.end = enddistance
				this.start = startdistance
				this.end = enddistance
			})
		},
	}
}	
</script>
<style scoped>
.charts {
	width: 100%;
	height: 200px;
}
</style>

3.画波形图(波形图的子页面)(创建一个名为curve.vue文件,可以跟父级页面同级方便调用,引用路径不用那么长)

<template>
	<view><canvas :canvas-id="cid" :id="cid" :style="`width: ${width}px; height: ${height}px;`" @click="onClick"></canvas></view>
</template>

<script>
//这里需要安装d3
import * as d3 from 'd3'

const fontSize = 12//字体大小
const padding = 22//内边距
const line1Color = '#0a2db8'//振动曲线的颜色
const line2Color = '#c20909'//敲击振动曲线的颜色
const bgColor = '#fff'//画布背景颜色
const gridColor = '#ccc'//网格的颜色
const axisColor = '#666'//X轴的颜色
const lineWidth = 0.5//线宽
const gridWidth = 1//网格的宽度
const seqColors = ['#06F506', '#c20909', '#0a2db8']//点时域曲线的颜色
export default {
	name: 'curve',
	props: {
		cid: {
			type: String,
			default: 'curve'
		},
		curve: {
			type: Object,
			default: () => ({})
		},
		xLabel: {
			type: String,
			default: '位置(m)'
		},
		points: {
			type: Array,
			default: function() {
				return [0, 0, 0]
			}
		}
	},
	data() {
		return {
			width: 300,//画布宽度
			height: 200,//画布高度
			max: 70,//y轴最大距离
			min: 0,//y轴最小距离
			yStep: 10,//y轴间隔
			start: 0,//x轴开始距离
			end: 800000//x轴结束距离
		}
	},
	computed: {
		scaleValue() {
			return d3
				.scaleLinear()
				.domain([padding, this.width - padding])
				.range([this.start, this.end])
		},
		xAxis() {
			return d3
				.scaleLinear()
				.domain([this.start, this.end])
				.range([padding, this.width - padding])
		},
		yAxis() {
			return d3
				.scaleLinear()
				.domain([this.min, this.max])
				.range([padding, this.height - padding])
		}
	},
	created() {
		// console.log('created ' + this.cid)
		// 获取窗口宽高
		const { windowWidth } = uni.getSystemInfoSync()
		this.width = windowWidth
		// console.log(this.width, this.height)
	},

	mounted() {
		// console.log('mounted ' + this.cid)
		this.ctx = uni.createCanvasContext(this.cid, this)
	},
	methods: {
		//曲线位置点击事件
		onClick(e) {
			// console.log('onClick', e.target.x, e.target.y)
			//这里能获取到你点击曲线的位置信息,便于后续显示数据
			let { x, y } = e.target
			this.$emit('curveClick', this.scaleValue(x))//传到父级页面使用
		},
		//获取父页面传过来的X轴的起始距离和结束距离
		setStartEnd(start, end) {
			this.start = start
			this.end = end
		},
		//绘制图表总事件
		drawChart(curve) {
			this.start = curve.start
			this.end = curve.end
			this.drawBackground()
			this.drawGrid()
			this.drawAxis()
			this.drawLegend()
			this.drawCurve(curve)
			this.drawMarkLine(curve)
			this.ctx.draw()
		},
		//绘制画布背景色
		drawBackground() {
			// console.log('drawBackground')
			const { ctx, width, height } = this
			ctx.beginPath()
			ctx.setFillStyle(bgColor)
			ctx.fillRect(0, 0, width, height)
			ctx.fill()
		},
		//绘制网格线
		drawGrid() {
			// console.log('drawGrid')
			const { ctx, yStep, min, max, width, height, xAxis, yAxis } = this
			ctx.beginPath()
			// ctx.setLineCap('round')
			ctx.setStrokeStyle(gridColor)
			ctx.setLineWidth(gridWidth)
			//绘制横向网格
			let count = max / yStep
			// console.log('count', count)
			let x = width - padding
			for (let i = 1; i <= count; i++) {
				let y = yAxis(max - i * yStep)
				ctx.moveTo(padding, y)
				ctx.lineTo(x, y)
			}
			//绘制纵向网格
			let ticks = xAxis.ticks(5)
			let y = height - padding
			for (let i = 0; i < ticks.length; i++) {
				let x = xAxis(ticks[i])
				ctx.moveTo(x, y)
				ctx.lineTo(x, padding)
			}
			ctx.stroke()//用 stroke() 方法来画线条

			//绘制y轴label
			ctx.beginPath()
			ctx.setFontSize(fontSize)
			ctx.setFillStyle('black')
			// ctx.fillText('0', padding - 6, height - padding + fontSize)
			ctx.fillText('衰耗(dB)', padding - 15, padding - 10)
			ctx.fillText(this.xLabel, width - 50, height - 30)
			for (let i = 0; i <= count; i++) {
				ctx.fillText('' + i * 10, padding - fontSize * 1.5, yAxis(max - i * 10) + fontSize / 2)
			}
			//绘制x轴label
			for (let i = 0; i < ticks.length; i++) {
				let num = ticks[i]
				let span = fontSize * 0.5
				if (num >= 10) {
					span = fontSize
				} else if (num >= 100) {
					span = fontSize * 1.5
				} else if (num >= 1000) {
					span = fontSize * 2
				} else if (num >= 10000) {
					span = fontSize * 2.5
				}
				ctx.fillText('' + ticks[i], xAxis(ticks[i]) - span, height - padding + fontSize + 2)
			}
		},
		//绘制X轴
		drawAxis() {
			// console.log('drawAxis')
			const { ctx, width, height } = this
			ctx.beginPath()
			ctx.setStrokeStyle(axisColor)
			ctx.setLineWidth(gridWidth)
			ctx.moveTo(padding, padding)
			ctx.lineTo(padding, height - padding)
			ctx.moveTo(padding, height - padding)
			ctx.lineTo(width - padding, height - padding)
			ctx.stroke()
		},

		// 绘制线条
		drawCurve(curve) {
			if (curve.data.length <= 0) {
				return
			}
			const { ctx, xAxis, yAxis, max, width } = this
			const data = curve.data
			const lineDataLen = data.length
			let sp = data[0]
			ctx.beginPath()
			ctx.setLineCap('round')
			ctx.setStrokeStyle(line1Color)
			ctx.setLineWidth(lineWidth)
			ctx.moveTo(xAxis(sp[0]), yAxis(max - sp[1]))
			for (let i = 0; i < lineDataLen; i++) {
				let x = xAxis(data[i][0])
				let y = yAxis(max - data[i][1])
				if (x < padding || x > width - padding) continue
				ctx.lineTo(x, y)
			}
			ctx.stroke()
		},
		//绘制敲击振动的曲线
		drawMarkLine(curve) {
			if (!curve.markLine || curve.markLine.length <= 0) {
				return
			}
			const { ctx, xAxis, yAxis, max, height } = this
			let markLine = curve.markLine
			ctx.beginPath()
			ctx.setStrokeStyle(line2Color)
			for (let i = 0; i < markLine.length; i++) {
				let x = xAxis(markLine[i][0])
				let y = yAxis(max - markLine[i][1])
				if (x < padding) continue
				ctx.moveTo(x, height - padding)
				ctx.lineTo(x, y)
			}
			ctx.stroke()
		},
		//绘制点击振动曲线的图例
		drawLegend() {
			const { ctx, points, width } = this
			let x = 60
			ctx.beginPath()
			for (let i = 0; i < points.length; i++) {
				ctx.setFillStyle(seqColors[i])
				ctx.fillRect(x, 2, fontSize, fontSize)
				x = x + fontSize + 2
				let t = points[i].toFixed(1) + '米'
				ctx.fillText(t, x, fontSize)
				let space = t.length * fontSize
				x = x + space - 10
			}
		}
	}
}
</script>

<style></style>

效果图如下:

在这里插入图片描述

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

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

相关文章

jQWidgets V15.0[2023-03-03] Crack

高级 JavaScript 和 HTML5 用户界面框架 jQWidgets 为构建专业网站和移动应用程序提供了全面的解决方案。它完全基于 HTML5、CSS 和 JavaScript 等开放标准和技术构建。jQWidgets 支持响应式 Web 开发&#xff0c;并帮助您创建在台式机、平板电脑和智能手机上看起来很漂亮的应…

Jenkins构建结果自定义内容推送企业微信

环境&#xff1a;gitlab、jenkins(2.400,windows环境下&#xff09;、企业微信 功能&#xff1a;推送构建人、构建时长、构建结果、git提交信息到企业微信并相应提交人 一、企业微信添加群聊机器人 在弹出的页面添加一个新的机器人&#xff0c;创建完成后会有一个webhook地址…

Django DRF - 认证Authentication

1.概念 身份验证是将传入请求与一组标识凭据&#xff08;例如&#xff0c;请求来自的用户或与其进行签名的令牌&#xff09;相关联的机制。然后&#xff0c;权限和限制策略可以使用这些凭据来确定是否应允许该请求。 身份验证本身不会允许或不允许传入的请求&#xff0c;它只…

最好用的中文分词库cppjieba_ok【可直接编译使用用版】

最好用的中文分词库cppjieba_ok【可直接编译使用用版】 中文分词技术在自然语言处理领域中扮演着非常重要的角色&#xff0c;其中cppjieba是一个高效、开源的中文分词库&#xff0c;它提供了多种分词算法和分词模式&#xff0c;并且能够支持多线程&#xff0c;能够大大提升分词…

计算机组成原理——第五章中央处理器(中)

辞别再无相见月&#xff0c;终是一人度春秋 文章目录前言5.4.1 硬布线控制器的设计5.4.2 微程序控制器的基本原理5.4.3 微指令的设计5.4.4 微程序控制单元的设计前言 本文主要写的是控制器的设计&#xff0c;控制器的设计分为硬部件控制器(就是用纯硬件的方式来实现的一种控制…

Asp net core写法

变量和字符串赋值 $"{变量}字符串" 列如 $"{a}你好" 全球唯一标识符 Guid.NewGuid() 线程 Task Map Dictionary<string,object> using 定义对象的使用范围&#xff0c;即使释放对象 using(Student student new Student() ) { } 异步编程&#x…

博客友链效果

学习风宇blog flex布局使用百分比作2栏网格布局&#xff0c;内部也是使用flex布局做左图右文使用IntersectionObserver这个浏览器提供的Api&#xff0c;配合vue指令&#xff0c;当元素出现在可视区时&#xff0c;添加上移的动画效果。注意&#xff1a;元素的初始状态&#xff…

Clion 创建的Qt程序界面是老式风格

环境&#xff1a; Clion版本&#xff1a;CLion 2023.1.1Qt 5.15.2 问题 当使用Clion创建Qt程序&#xff0c;不管是选择MinGW编译器还是MSVC编译器&#xff0c;显示出来的界面都很old style 即老式的界面风格&#xff0c;如图所示&#xff1a; 原因 产生这个问题的原因是…

现在培训IT技术出来还能就业吗?

受疫情影响&#xff0c;近三年各行业都是在裁员节流&#xff0c;直接倒闭的也是常态。互联网企业也不能幸免于难&#xff0c;被裁掉了一大波基础或中级技术岗位&#xff0c;还有一部分中高层领导降薪。然后就到处疯传IT行业不行了&#xff0c;大家都失业了&#xff0c;但你知道…

算法训练第五十六天 | 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇

这里写自定义目录标题583. 两个字符串的删除操作题目描述思路动态规划一动态规划二72. 编辑距离&#xff08;困难&#xff09;题目描述思路编辑距离总结篇583. 两个字符串的删除操作 题目链接&#xff1a;583. 两个字符串的删除操作 参考&#xff1a;https://programmercarl.c…

clickhouse跳表索引最佳实践--minmax索引+Set索引

背景 我们知道对于clickhouse这种列式存储数据库来说&#xff0c;它虽然也提供了与mysql的btree的二级索引类似的跳表索引的功能&#xff0c;但是其实跳表索引和btree的二级索引有根本上的不同&#xff0c;比如mysql的二级索引一般来说应用于高基数的列的效果是最佳的&#xf…

SpringMVC入门以及工作流程详解

什么是MVC MVC是一种分层方法&#xff0c;其三层分别是模型&#xff08;Model&#xff09;&#xff0c;视图&#xff08;View&#xff09;和控制器&#xff08;Controller&#xff09;。是一种软件设计规范。MVC可以将软件的逻辑、数据和业务相分离&#xff0c;其主要作用是消除…

SpringCloud-Alibaba学习笔记03——nacos管理界面详细介绍和注册中心配置详解

一、nacos管理界面详细介绍 nacos服务管理界面如图所示&#xff1a; 1、创建服务和隐藏空服务 我们可以看到在界面上有个隐藏空服务的按钮&#xff0c;我们打开该按钮之后&#xff0c;服务列表并没有发生变化&#xff0c;这个按钮有啥用呢&#xff1f; 该按钮的作用就是隐藏…

St link V2驱动安装方法

前言 st-link v2又便宜又好用&#xff0c;是ARM单片机开发的必备神器。本文面向初学者介绍如何给windows安装stlink 驱动&#xff0c;并在keil v5进行配置。 操作系统&#xff1a;windows11&#xff0c; Keil版本&#xff1a;keil v5 mdk5.37. st-link驱动云盘共享地址&#x…

OSS下载中文名编码错误

最近工作中有个需求&#xff0c;是将客户支付的银行回执单上按照客户姓名上传到oss&#xff0c;然后将oss地址反显到pc后台&#xff0c;供客户自己查看下载。 开始的时候感觉很简单&#xff0c;设计思路是根据客户支付单单号&#xff0c;查询数据库中是否存在该支付单的回执单&…

Java生成二维码之Graphics2D自定义码眼形状

Java 2D API 提供了几个类来定义常见的几何对象&#xff0c;例如点、直线、曲线和矩形。这些几何类是 java.awt.geom包的一部分。通过熟练使用Graphics2D类&#xff0c;可以绘制出任意类型的图形。 官网教程地址&#xff1a;https://docs.oracle.com/javase/tutorial/2d/geome…

【虹科案例】固态量子发射器——虹科数字化仪用于控制钻石色心中的脉冲序列

前言 钻石的色心是晶格中的缺陷&#xff0c;其中碳原子被不同种类的原子取代&#xff0c;相邻的晶格位置是空的。由于其明亮的单光子发射和光学可访问的自旋&#xff0c;色心可以成为未来量子信息处理和量子网络的有前途的固态量子发射器。 实现自旋量子比特和相干光子纠缠的两…

基于RK3568的Linux驱动开发—— GPIO知识点(二)

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主系列基于RK3568的Linux驱动开发——GPIO知识点&#xff08;一&#xff09;_daisy.skye的博客-CSDN博客 查看goio使用情况 cat /sys/kernel/debug/gpio 1|rk3568_r:# cat /sys/kernel/debug/gpio gpiochip0: GPIOs 0-3…

English Learning - L2-14 英音地道语音语调 重音技巧 2023.04.10 周一

English Learning - L2-14 英音地道语音语调 重音技巧 2023.04.10 周一课前热身重音日常表达节奏单词全部重读的句子间隔时间非重读单词代词和缩约词助动词声临其境语调预习课前热身 学习目标 重音 重弱突出&#xff0c;重音突出核心表达的意思 重音是落在重读单词上&#x…

Vue3简介

1.Vue3简介 2020年9月18日&#xff0c;Vue.js发布3.0版本&#xff0c;代号&#xff1a;One Piece&#xff08;海贼王&#xff09;耗时2年多、[2600次提交](https://github.com/vuejs/vue-next/graphs/commit-activity)、[30个RFC](https://github.com/vuejs/rfcs/tree/master/…