JavaScript獲取echart曲線上任意點(diǎn)位的值詳解
需求背景
智慧農(nóng)業(yè)里有一個(gè)很重要的功能是控制溫室生長(zhǎng)環(huán)境,讓農(nóng)作物生長(zhǎng)的更好。于是,我們需要在曲線上根據(jù)不同農(nóng)作物設(shè)置不同的環(huán)境數(shù)據(jù)。為了方便用戶修改這些數(shù)據(jù),我們需要支持用戶在曲線上進(jìn)行拖拽,并且拖拽后還需要把拖拽后的關(guān)鍵點(diǎn)位返回給后端。后面溫度傳感器收到溫度數(shù)據(jù)后,會(huì)發(fā)給后端和前端。兩端都需要判斷傳過(guò)來(lái)的溫度是否在曲線之間。如果超出或者下沉需要觸發(fā)相應(yīng)的事件。
需求調(diào)研
本來(lái)以為這個(gè)需求很簡(jiǎn)單,導(dǎo)入echarts,設(shè)置兩條曲線,并支持拖動(dòng)。拖動(dòng)。這些都屬于echarts開(kāi)放的功能,但等到要查詢某個(gè)時(shí)間點(diǎn)曲線上的值時(shí),才發(fā)現(xiàn)echarts根本就沒(méi)開(kāi)放這個(gè)功能,查看源碼發(fā)現(xiàn)他是用貝塞爾算法生成的曲線,好吧,既然你不支持,那就只能自己實(shí)現(xiàn)了。
后面找了下資料,發(fā)現(xiàn)一個(gè)貝塞爾的js開(kāi)源庫(kù),看了下它的說(shuō)明,發(fā)現(xiàn)有g(shù)et(t)方法獲取到曲線上Y軸的值。參數(shù)t表示兩個(gè)關(guān)鍵點(diǎn)之間x軸值。由于我們傳入的x軸是時(shí)間,所以這個(gè)t就是x軸的時(shí)間。
所以,理論上,如果兩個(gè)使用的算法是一致的,那么從曲線上獲得的值就應(yīng)該是一樣的。
實(shí)現(xiàn)步驟
第一步,安裝bezier-js
npm install bezier-js
第二步,初始化貝塞爾對(duì)象
constructor(input = {points: [20, 30, 40], ts: [0, 60000 * 60 * 24]}) { this.line = null this.keyPoints = []//根據(jù)輸入input,生成關(guān)鍵的點(diǎn)坐標(biāo)數(shù)組 [ x1坐標(biāo),y1坐標(biāo), x2坐標(biāo),y2坐標(biāo) ] this.tsStart = 0 this.tsEnd = 0 this.init(input) this.input = input } init(input) { try { if (![input].$has('ts')) input.ts = [0, 60000 * 60 * 24]//如果沒(méi)有ts時(shí)間,默認(rèn)賦值為1天的時(shí)間周期 if (![input].$has('points', 'ts') || input.points.length < 2 || input.ts.length !== 2) { console.log('【bezier】錯(cuò)誤!實(shí)例化曲線時(shí)參數(shù)錯(cuò)誤') return//最小2個(gè)點(diǎn) } this.tsStart = Number(input.ts[0]) this.tsEnd = Number(input.ts[1]) let tsLen = this.tsEnd - this.tsStart if (tsLen < 0) { console.log('【bezier】錯(cuò)誤!實(shí)例化曲線時(shí)參數(shù)錯(cuò)誤,時(shí)間長(zhǎng)度錯(cuò)誤') return } let stepLen = tsLen / (input.points.length - 1) let points = [] let x = input.ts[0] input.points.forEach(y => { points.push({x: x}) points.push({y: y}) x += stepLen }) points.splice(points.length - 2, 2)//刪除最后一組坐標(biāo)(2個(gè)元素) points.push({x: input.ts[1]})//再用input的值填充,避免因?yàn)槠骄祵?dǎo)致精度的問(wèn)題 points.push({y: input.points[input.points.length - 1]}) this.keyPoints = points this.line = new Bezier(points) // points = [ x1坐標(biāo),y1坐標(biāo), x2坐標(biāo),y2坐標(biāo) ] console.log('生成貝塞爾曲線的數(shù)據(jù)', this.line, points) } catch (e) { console.log('【bezier】錯(cuò)誤!在實(shí)例化對(duì)象時(shí)錯(cuò)誤', e) } }
第三步,我們曲線上看到的是時(shí)間是“時(shí)+分”格式,傳入的要轉(zhuǎn)化為時(shí)間戳
// 獲取指定時(shí)間戳的y值數(shù)據(jù) // ts橫坐標(biāo),時(shí)間戳 // 輸出 {x,y} ,x就是 ts get(ts = 0) { let duration if (ts <= this.tsStart) { duration = 0 } else if (ts <= this.tsEnd) { duration = ts - this.tsStart } else { duration = this.tsEnd - this.tsStart } let b = duration / (this.tsEnd - this.tsStart) return this.line.get(b) // b取值0~1,小數(shù).0表示曲線第一個(gè)點(diǎn),1表示最后1個(gè)點(diǎn) }
第四步,服務(wù)端給我們的關(guān)鍵點(diǎn)位可能有幾十個(gè),如果我通過(guò)幾十個(gè)點(diǎn)生成貝塞爾對(duì)象,在通過(guò)get值去查詢時(shí)候,由于它內(nèi)部的算法,除了收尾兩個(gè)關(guān)鍵點(diǎn)外,其他關(guān)鍵點(diǎn)位根據(jù)時(shí)間查詢出的Y值會(huì)有偏差。這跟我們?cè)趀charts曲線上看到的值并不一樣。我猜原因在于echarts的平滑曲線并不是由首尾兩個(gè)點(diǎn)生成的,而是由兩個(gè)兩個(gè)關(guān)鍵點(diǎn)生成一段一段平滑曲線組合而成的。
為了驗(yàn)證我的猜想,我寫(xiě)了個(gè)demo,通過(guò)6個(gè)關(guān)鍵點(diǎn)在echarts上生成了二條曲線。同時(shí),我通過(guò)bezier-js的get(t)方法獲取更多點(diǎn)位,然后放到echars上,看兩者的曲線是否重合。
事實(shí)證明,我的猜想是對(duì)的。兩者曲線是重合的。
echarts 根據(jù)六個(gè)點(diǎn)生成的曲線
下圖是根據(jù)兩個(gè)關(guān)鍵點(diǎn)之間計(jì)算多個(gè)節(jié)點(diǎn)組合成的曲線
所以,我們?cè)谟?jì)算某個(gè)時(shí)間軸的Y軸時(shí),要先判斷查詢的X軸時(shí)間屬于哪兩個(gè)點(diǎn)之間,然后再用get(t)方法查Y值
/** * 查詢曲線上某個(gè)時(shí)間點(diǎn)曲線上具體的值,這里查詢的是上限。 */ getPointValueByTime() { // 把時(shí)間轉(zhuǎn)換成毫秒 let time = this.timeToSec(this.defaultTime) // 判斷這個(gè)時(shí)間在哪兩個(gè)關(guān)鍵點(diǎn)之間 // 先獲取曲線上所有時(shí)間點(diǎn)的數(shù)組 let preData = [] for (var i = 0; i < upperLimit.length; i++) { let itemTime = this.timeToSec(upperLimit[i][0]) // 如果查詢的時(shí)間小于當(dāng)前的item,這表示在這個(gè)item和上一個(gè)item之間。則通過(guò)這兩個(gè)item生成貝塞爾對(duì)象,然后用get方法查詢time值 if (time <= itemTime && i != 0) { // 查詢的點(diǎn)就是preData 和item的數(shù)組 let pointArray = [] pointArray.push(preData[1]) pointArray.push(upperLimit[i][1]) const bezier = new BezierLine({points: pointArray, ts: [preData[0], itemTime]}) let data = bezier.get(time) console.log('查詢到當(dāng)前時(shí)間' + time + '的值為', data) this.searchValueByTime = parseFloat(data.y).toFixed(2) break } else { // 上一個(gè)不符合的對(duì)象 preData = [itemTime, upperLimit[i][1]] } } },
到此,我們就可以通過(guò)任意一個(gè)X軸時(shí)間來(lái)獲得對(duì)應(yīng)的Y軸value值了。當(dāng)然,文章里只是把實(shí)現(xiàn)思路講了,貼出來(lái)的代碼不是全部,只是供大家理解。完整版代碼我已經(jīng)上傳到碼云,如果有什么bug,大家可以留言告知一下。 下載地址:GetEchartsCurveData
以上就是JavaScript獲取echart曲線上任意點(diǎn)位的值詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript echart曲線任意點(diǎn)位值的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue-pdf打包后無(wú)法預(yù)覽問(wèn)題及修復(fù)方式
這篇文章主要介紹了vue-pdf打包后無(wú)法預(yù)覽問(wèn)題及修復(fù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03vue loadmore 組件滑動(dòng)加載更多源碼解析
這篇文章主要介紹了vue loadmore 組件滑動(dòng)加載更多源碼解析,需要的朋友可以參考下2017-07-07vue使用pinia實(shí)現(xiàn)全局無(wú)縫通信
這篇文章主要為大家詳細(xì)介紹了vue如何使用pinia實(shí)現(xiàn)全局無(wú)縫通信,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下2023-11-11vue.js中methods watch和computed的區(qū)別示例詳解
methods,watch和computed都是以函數(shù)為基礎(chǔ)的,但各自卻都不同,這篇文章主要給大家介紹了關(guān)于vue.js中methods watch和computed區(qū)別的相關(guān)資料,需要的朋友可以參考下2021-08-08Vue路由history模式解決404問(wèn)題的幾種方法
這篇文章主要介紹了Vue路由history模式解決404問(wèn)題的幾種方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09使用vant-picker實(shí)現(xiàn)自定義內(nèi)容,根據(jù)內(nèi)容添加圖標(biāo)
這篇文章主要介紹了使用vant-picker實(shí)現(xiàn)自定義內(nèi)容,根據(jù)內(nèi)容添加圖標(biāo),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12