用canvas画曲线图

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>

效果图如下:

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值