1.创建 canvas 绘图上下文(指定 canvasId)
定义:在自定义组件下,第二个参数传入组件实例this,以操作组件内 canvas 组件。需要指定 canvasId,该绘图上下文只作用于对应的 canvas。
参数
参数 | 类型 | 说明 |
---|---|---|
canvasId | String | 画布表示,传入定义在 canvas组件的 canvas-id或id(支付宝小程序是id、其他平台是canvas-id) |
componentInstance | Object | 自定义组件实例 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>
效果图如下: