> 文章列表 > 用canvas画曲线图

用canvas画曲线图

用canvas画曲线图

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)//用户tokenlet url = getBaseUrl().replace('https://', 'ws://').replace('http://', 'ws://')url = url + '/ifcs/websocket/wave/' + token + '/' + this.task.devicecodeconsole.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.maxlengththis.rangMax = data.maxlengththis.range[1] = data.maxlengththis.end = data.maxlength}// 波形let array = data.originalDataListlet length = data.originalDataList.length// console.log('波形length: ' + length)if (length > 0) {const temp = []//计算数据抽取力度let points = 300 //图表内显示的点数let step = 10if (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.datacurveData.start = startdistancecurveData.end = enddistancethis.start = startdistancethis.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.targetthis.$emit('curveClick', this.scaleValue(x))//传到父级页面使用},//获取父页面传过来的X轴的起始距离和结束距离setStartEnd(start, end) {this.start = startthis.end = end},//绘制图表总事件drawChart(curve) {this.start = curve.startthis.end = curve.endthis.drawBackground()this.drawGrid()this.drawAxis()this.drawLegend()this.drawCurve(curve)this.drawMarkLine(curve)this.ctx.draw()},//绘制画布背景色drawBackground() {// console.log('drawBackground')const { ctx, width, height } = thisctx.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 } = thisctx.beginPath()// ctx.setLineCap('round')ctx.setStrokeStyle(gridColor)ctx.setLineWidth(gridWidth)//绘制横向网格let count = max / yStep// console.log('count', count)let x = width - paddingfor (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 - paddingfor (let i = 0; i < ticks.length; i++) {let x = xAxis(ticks[i])ctx.moveTo(x, y)ctx.lineTo(x, padding)}ctx.stroke()//用 stroke() 方法来画线条//绘制y轴labelctx.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轴labelfor (let i = 0; i < ticks.length; i++) {let num = ticks[i]let span = fontSize * 0.5if (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 } = thisctx.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 } = thisconst data = curve.dataconst lineDataLen = data.lengthlet 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) continuectx.lineTo(x, y)}ctx.stroke()},//绘制敲击振动的曲线drawMarkLine(curve) {if (!curve.markLine || curve.markLine.length <= 0) {return}const { ctx, xAxis, yAxis, max, height } = thislet markLine = curve.markLinectx.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) continuectx.moveTo(x, height - padding)ctx.lineTo(x, y)}ctx.stroke()},//绘制点击振动曲线的图例drawLegend() {const { ctx, points, width } = thislet x = 60ctx.beginPath()for (let i = 0; i < points.length; i++) {ctx.setFillStyle(seqColors[i])ctx.fillRect(x, 2, fontSize, fontSize)x = x + fontSize + 2let t = points[i].toFixed(1) + '米'ctx.fillText(t, x, fontSize)let space = t.length * fontSizex = x + space - 10}}}
}
</script><style></style>

效果图如下:

在这里插入图片描述