<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          數(shù)據(jù)可視化在滴滴的應(yīng)用

          共 11471字,需瀏覽 23分鐘

           ·

          2021-06-24 14:31

          桔妹導讀:現(xiàn)代的數(shù)據(jù)可視化產(chǎn)品相較于之前的儀表盤應(yīng)用,在數(shù)據(jù)方面呈現(xiàn)更加生動、數(shù)據(jù)實時性高、交互更為友好、效果更加震撼等特點,越來越多的人傾向于通過各類可視化產(chǎn)品使靜態(tài)的數(shù)據(jù)“活”起來。基于此背景,我們結(jié)合滴滴的各業(yè)務(wù)線發(fā)展,打造了本文介紹的數(shù)據(jù)可視化大屏產(chǎn)品。



          1. 

          前言

          隨著技術(shù)的發(fā)展,更多的人不滿足于使用基礎(chǔ)的圖表來展示數(shù)據(jù),如何讓數(shù)據(jù)更直觀、更炫酷的展示成為了大家的追求。為了能更好的展示滴滴各業(yè)務(wù)線的發(fā)展,本文介紹的數(shù)據(jù)可視化大屏從城市、全國和全球維度展示了業(yè)務(wù)線的實時信息,下面將從基礎(chǔ)地圖的構(gòu)建、軌跡、氣泡、熱力、飛線、散點六個方面來講述一下開發(fā)中遇到的難題及解決方案。


           


          2. 
          開發(fā)流程
          前期主要是產(chǎn)品側(cè)對接需求,同時與設(shè)計師側(cè)打磨設(shè)計稿,泛前端團隊的介入主要是在拿到產(chǎn)品設(shè)計稿后,將精力聚焦在以下方面:

          1.難點和潛在性問題梳理

          • 1)地圖的一次性加載,考慮到易用性和維護性,需自研一套地圖框架;

          • 2)大量數(shù)據(jù)涉及到的性能問題,包含數(shù)據(jù)的計算、傳輸和實時渲染;

          • 3)數(shù)據(jù)業(yè)務(wù)方較多,接口穩(wěn)定性和維護性問題;

          • 4)可視化還原度;


          2.技術(shù)攻堅

          • 1)自研地圖框架map3;

          • 2)將數(shù)據(jù)計算移到后端,對數(shù)據(jù)進行壓縮,同時在大量數(shù)據(jù)的傳輸方面采用ArrayBuffer。盡量減輕GPU和CPU的壓力,提高頁面流暢度。

          • 3)因為數(shù)據(jù)大屏對數(shù)據(jù)的依賴性較強,為了保障展示的穩(wěn)定性,對數(shù)據(jù)采取了緩存兜底方案;

          • 4)通過開發(fā)調(diào)試面板,降低與設(shè)計師溝通的成本,為提高可視化還原度提供了便利。


          3.打磨驗收

          通過上文提到的調(diào)試面板以及錄屏,和設(shè)計師開啟遠程調(diào)試階段。此外在驗收過程中還涉及到各個模塊的版本迭代,每次驗收之后可能都會涉及到相關(guān)模塊樣式及功能的改動,甚至是整個大屏的變動,所以整個不斷調(diào)整和優(yōu)化的階段也是非常耗時的。

          該數(shù)據(jù)可視化大屏采用的是webgl等技術(shù),在瀏覽器端對渲染的效果進行展示。webgl是一個較為冷門的話題,遇到的很多問題很難直接找到通用的解決方案,更多的是團隊人員的一些思考,所以可能并不是最完美的。下面將從基礎(chǔ)地圖的構(gòu)建、軌跡、氣泡、熱力、飛線、散點六個方面來簡單介紹一下。



          3. 
          地圖構(gòu)建

          圖3.1 北京屏.gif

          以北京為例,我們對整個北京市進行了建模,包括道路、水系、建筑等,此外還對10余個地標性建筑進行了精細處理,例如望京SOHO、CCTV、故宮、工人體育館、中關(guān)村等是以精模形式展現(xiàn)在地圖上。總體以較為沉浸式的感官體驗進行展示,通過軌跡、訂單位置和訂單熱力等對滴滴的業(yè)務(wù)進行了實時的可視化展示。

          1.技術(shù)選擇

          要構(gòu)建圖片中的城市,首先想到的肯定是借助強大的開源社區(qū)。目前大多數(shù)的開源地圖框架都支持3d矢量繪制,例如mapbox、cesium等,mapbox的官網(wǎng)中也已經(jīng)展示了與threejs結(jié)合的示例,但是對于北京屏的場景,開源顯得還是有些不夠用,主要體現(xiàn)在一下幾點:

          1)效果方面
          使用mapbox,瓦片數(shù)據(jù)會隨著視角的移動重新加載,這對于移動應(yīng)用或者普通的pc是件好事,但是對于大屏場景反而成了問題。我們需要一次性把道路、建筑等地圖信息都加載進來,在鏡頭移動的過程中不能出現(xiàn)重新加載這種十分影響體驗感的現(xiàn)象。

          2)易用性、可維護性方面
          北京屏需要使用大量的webgl,我們選擇了threejs這一套庫來承接。mapbox官網(wǎng)中展示了與threejs結(jié)合的代碼示例,但是其中涉及到了大量threejs與mapbox矩陣的轉(zhuǎn)換,所以如果選用mapbox,就需要開發(fā)人員和后續(xù)維護人員都非常熟悉這一套繁瑣的轉(zhuǎn)換規(guī)則,可能還需要進一步深挖mapbox底層的渲染邏輯。

          3)性能方面
          北京屏需要繪制、實時處理的數(shù)據(jù)量都非常大,加之頁面中鏡頭還需要不同的運動,所以GPU和CPU負載都非常高,項目中我們已經(jīng)使用了非常高配置的顯卡和CPU,但是在開發(fā)上,我們?nèi)匀恍枰M可能的減少性能消耗。如果使用mapbox與threejs結(jié)合的方式,如何把性能做到最優(yōu)是一個很大的問題,因為涉及到兩個框架在很多方面的協(xié)調(diào)問題。

          所以綜合以上三點的考慮,我們決定在現(xiàn)有技術(shù)的基礎(chǔ)上,研發(fā)一套地圖框架map3。這套庫在渲染上選擇了threejs,API設(shè)計上參考了mapbox,非常適合大屏可視化場景。

          2.技術(shù)選擇

          map3的使用方法和mapbox十分相似,傳入以下配置項我們就可以渲染出對應(yīng)的地圖:

          const map3 = new Map3({ container: string | HTMLElement; style: Style; center?: number[]; pitch?: number; bearing?: number; zoom?: number; controller?: boolean; hash?: boolean;});
          圖3.2 map3使用示例

          其中,style是我們傳入的樣式數(shù)據(jù),針對本項目中的北京地圖,style文件中包含了背景、燈光、天空、結(jié)構(gòu)(建筑、道路、水、綠地等數(shù)據(jù))、精模(望京SOHO、CCTV、故宮、工人體育館、中關(guān)村等精細模型數(shù)據(jù))。通過在map3中對style數(shù)據(jù)一層層的解析和渲染,最終我們可以得到一次性加載完成的北京地圖。

          3.數(shù)據(jù)壓縮

          項目中需要用threejs來繪制整個北京城,包括道路、建筑、綠地、山脈等,數(shù)據(jù)量超過300M,且要求一次性加載完畢。平時js文件超過2M就要考慮優(yōu)化,300M已經(jīng)突破了瀏覽器的最大限制,即這么大的文件瀏覽器不會緩存(當然可以通過設(shè)置來更改這個最大限制,但是首次加載是必須的),所以要考慮如何壓縮文件。

          mapbox的geobuf可以解決這個問題。geobuf能以近乎無損的方式將geojson壓縮6-8倍,即使經(jīng)過gzip處理也能壓縮2-2.5倍。通過源碼大致了解其壓縮原理,下面寫出來與大家交流下,有不完善的地方望指正。

          geobuf使用protocol buffers作為數(shù)據(jù)存儲的格式,protocol buffers是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,可以用于結(jié)構(gòu)化數(shù)據(jù)串行化,或者說序列化。它很適合做數(shù)據(jù)存儲或RPC數(shù)據(jù)交換格式。它是與語言無關(guān)、平臺無關(guān)、可擴展的序列化結(jié)構(gòu)數(shù)據(jù)格式。關(guān)于protocol buffers的介紹這里不再贅述,大家可以參考官網(wǎng)或網(wǎng)絡(luò)上的一些介紹,這里只重點指出與geobuf相關(guān)的一個特性,就是varint。varint的介紹及原理,也可以參考官網(wǎng)或https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html,這里直接說下其特點。varint的特點就是存儲較小的數(shù)據(jù)所用的字節(jié)數(shù)很少,例如存儲1、2、3這樣的數(shù)字,就只用1個字節(jié)。varint的這個特性與后面所要講的geobuf的壓縮有很大關(guān)系,所以這里重點強調(diào)下。

          geobuf把數(shù)據(jù)壓縮的那么小,還是涉及很多方面的優(yōu)化,下面我將對個人認為壓縮得最厲害的地方做一個介紹。以下是一段geojson,描述了一段LineString,geobuf并不是直接使用浮點數(shù)來存儲這些數(shù)值,而是將數(shù)值做了一個計算,除第一組經(jīng)緯度外,其他經(jīng)緯度取與前面經(jīng)緯度的差值,再將差值乘1e5(為了方便介紹,后面這一步暫時稱為整型化,具體是乘1e5還是1e6或者其他值,取決于geojson本身的精度)。

          圖3.3 geojson數(shù)據(jù)示例

          以上面的數(shù)據(jù)為例,取差值后的值為[[120.07045, 30.20248], [0.00001, -0.00022], [-0.00001, -0.00009]...],整型化后的值為[[12007045, 3020248], [1, -22], [-1, -9]...]。geojson描述的無論是polygon還是linestring,coordinates里面的經(jīng)緯度值其實都比較接近,所以處理后的數(shù)值都會比較小,這樣再使用varint來存儲就會得到比較好的壓縮效果。

          最后說下項目中對geobuf的改進。主要包含以下兩點:


          • 1)因為項目中繪制的區(qū)域相對較小,例如杭州,范圍在東經(jīng)118°21′-120°30′,北緯29°11′-30°33′ °,這樣對于每一條LineString或者polygon的首個點來說,存儲全部的數(shù)值也存在一些浪費,于是我們在計算前加入了一個減法計算,將所有的經(jīng)緯度減去一組數(shù)值,例如上面的例子中,減去[120, 30],這樣數(shù)值將變?yōu)閇[7045, 20248], [1, -22], [-1, -9]...],首個點的數(shù)值更小了,所需要的字節(jié)數(shù)也更少了(這一步雖然減小了一點數(shù)據(jù)量,但失去了靈活性,需要權(quán)衡利弊)。

          • 2)地圖繪制都存在投影計算,我們將投影計算放在了encode的代碼中,瀏覽器接收到數(shù)據(jù)decode后可以直接使用,這步可以減小瀏覽器的計算,節(jié)省cpu和計算時間的開支。


          改良后的geobuf叫做mbuf,下面是一組對比數(shù)據(jù):(注:geobuf和mbuf還剔除了一些不需要的屬性,所以geobuf相比官方提供的測試數(shù)據(jù)壓縮比更大,官方提供的數(shù)據(jù)中g(shù)zip后可壓縮2-2.5倍)


          4.可視化還原


          圖3.4 數(shù)據(jù)大屏設(shè)計稿


          上圖為北京屏的設(shè)計稿。設(shè)計稿是一張二維的圖片,需要將此二維圖片還原到3d場景,但是二維可以設(shè)置的參數(shù)與三維完全不一致,例如三維中通常需要設(shè)置材質(zhì)、光線的屬性,而這些參數(shù)二維中是沒有的,因此一開始只能憑經(jīng)驗靠感覺來調(diào)整。但是對于開發(fā)來說,如何恰到好處的把各個參數(shù)調(diào)整到位是一件非常頭疼的事情,而且很難精準的達到設(shè)計師想要的效果。問題不好解決,溝通成本也很高,不如一勞永逸,開發(fā)一個工具給設(shè)計師調(diào)整,于是就有了map3中的configer工具。通過可視化的調(diào)試面板,讓設(shè)計師通過調(diào)整參數(shù)來還原場景效果,在附加上一鍵導出所有配置參數(shù)的功能,保留配置并直接應(yīng)用在map3中,這一切就變得簡單了許多。

          圖3.5 map3配置面板

          工具還附帶可水紋效果、背景效果,可以把three.js官網(wǎng)中的示例很方便的接入進來,還可以使用threejs中的各種材質(zhì),添加多種類型的燈光。



          4. 
          軌跡
          圖4.1 軌跡.gif

          這部分主要介紹的是上圖長軌跡的實時繪制,同時這也是影響性能的很重要的一部分。正式展開敘述之前先看下需求是怎樣的。如動圖中所示,需要獲取實時軌跡數(shù)據(jù)在前端進行展示,軌跡需要流動起來,且在地圖視野拉近(近看城市)時運動變慢、軌跡變細,在地圖視野拉遠時(俯視北京全城)運動變快、軌跡變粗。效果要反映真實的訂單情況,所以數(shù)據(jù)需要實時更新。下面從幾個重要的點來展開介紹。


          1.繪制軌跡

          受Chrome的限制,webgl繪制線條的時候只能繪制1px。因此我們只能通過繪制面的方式來繪制線。關(guān)于繪制方式,網(wǎng)上有大量的文章,這里不做贅述(eg:https://zhuanlan.zhihu.com/p/35837423)。畫好線條之后再將紋理映射到線條上即可。

          2.軌跡運動

          如何讓軌跡動起來呢?其實就是每一幀都讓軌跡沿著預(yù)設(shè)好的路線向前移動。設(shè)想軌跡有一個頭和一個尾,每幀讓頭和尾都沿著路線移動固定的距離,再將頭尾間的點連接起來即可。通常我們會用若干點來描繪一條路徑(下面我們稱這些點為路徑點,即下圖所示的實心點),點在路徑上并不是均勻分布的,在轉(zhuǎn)彎的地方會比較密集,直線的地方相對稀疏。所以在確認好頭尾點的位置后,還需要將頭尾點與中間的路徑點串聯(lián)起來,才是我們最終需要繪制的軌跡。如下圖所示,紅色曲線是需要繪制的部分。

          圖4.2 軌跡示意圖1

          所以如何定位首尾點的位置是重點。假設(shè)軌跡長為length,每幀向前移動x米,軌跡可見部分,即頭尾長y米,第z幀的時候,head的位置就在距離起點x*z米的地方,但如果x*z>length,即head位置超過軌跡的總長度,則head的位置為length處,所以為min(x*z, length);tail的位置需要判斷有沒有在可視范圍內(nèi),所以值為max(0, x*z - y)。

          那么現(xiàn)在問題又歸結(jié)到如何在路徑上找到距離起始點特定長度的點的坐標。大家可能會想到使用turf的distance方法, 使用起來很方便,輸入兩個點的坐標就可以得到兩點間的距離,所以一開始我們也是想到使用turf,這也是埋的一個坑。turf的性能很差,原因就是每次計算兩點之間距離的時候都會重新計算已經(jīng)計算過的點,造成了性能上的浪費,畫不了幾條長軌跡就會卡,所以另尋他法。后來我們采用動態(tài)規(guī)劃的思路,方法如下:

          圖4.3 軌跡示意圖2

          以head為例,記錄下上一幀head的位置即為上圖中的lastPoint,以及于head最接近的路徑點的位置p1,假設(shè)每幀需要移動的距離是dis,下一步就是判斷l(xiāng)astPoint于p1的距離(設(shè)為d1)是否大于dis。如果d1 > dis,說明下一個點在lastPoint和p1之間,這樣通過比例計算就能找到需要的位置;如果d1 < dis,說明下一個點在p1之后,接著讓dis=dis-d1,然后判斷p1和p2間的距離是否大于dis,以此類推就可以找到下一個頂點的位置。有了頭尾的位置,再將頭尾與路徑點連接起來就可以得到當前需要繪制的軌跡。

          3.數(shù)據(jù)更新

          為了減輕前端的壓力,我們將計算基本都移到了后端進行,例如軌跡的每次移動都是重新從后端獲取的計算數(shù)據(jù)。考慮到數(shù)據(jù)更新非常頻繁,我們使用websocket來傳輸數(shù)據(jù),但是還是會存在一些問題。通常我們傳輸數(shù)據(jù)會使用json,但是對于北京屏的場景,json就不太適用。從動圖中我們可以看到在北京屏的場景中,鏡頭一直在動,氣泡一直在冒,軌跡一直在跑,所以稍微一個比較大的計算就會造成頁面的卡頓,單個計算的時間需要控制在20ms內(nèi),一個JSON.parse()的使用就會明顯感覺到頁面的卡頓。所以在數(shù)據(jù)傳輸方面采用了ArrayBuffer。除了在傳輸格式上需要注意,另一個需要注意的點就是GC,對于float32Array等浮點型數(shù)據(jù),需要提前分配好內(nèi)存,否則又會出現(xiàn)頁面的卡頓。



          5. 
          氣泡
          圖5.1 氣泡.gif

          如上圖所示,不斷冒出的氣泡代表了各業(yè)務(wù)線訂單的生成,成為了場景中不可或缺的一部分,感官上使北京屏更加豐盈。在開發(fā)過程中,設(shè)計師給的是.webp文件,開始時是想通過參考mapbox中添加動態(tài)marker的方式,我們將.webp文件以marker的方式添加到dom中。缺陷是這種情況下marker是不斷重復輪播的,除非我們手動將對應(yīng)的dom移除,否則頁面上的marker會越來越多。但是我們?nèi)绾闻袛嗝總€marker一次動畫播放結(jié)束從而將其移除呢?因為每個訂單生成的時間不一致,所以動畫開始和結(jié)束的時間不一致,很難判斷。再者就算可以判斷,頻繁對dom進行添加和刪除的操作也是欠妥的。最后我們將這些氣泡以mesh的形式添加到scene中,在render的過程中不斷更換氣泡每幀對應(yīng)的紋理來實現(xiàn)運動的效果。

          1.確定位置

          圖5.2 確定氣泡位置示意圖

          圖(a)中o點是根據(jù)傳過來的經(jīng)緯度轉(zhuǎn)換成的地圖中的坐標。數(shù)據(jù)是以一組經(jīng)緯度來代表訂單生成的位置,我們要在這個位置顯示氣泡,webgl中是以一個三角形片元繪制各種圖形,矩形至少需要兩個三角形片元,6個頂點,除去可以共用的頂點,我們至少要知道圖(b)中0、1、2、3四個頂點的坐標。如圖所示,我們將經(jīng)緯度對應(yīng)的點作為矩形底邊的中點,結(jié)合圖片的寬高即可得到0、1、2、3四個點的坐標。

          2.繪制紋理

          圖(a)中o點是根據(jù)傳過來的經(jīng)緯度轉(zhuǎn)換成的地圖中的坐標。數(shù)據(jù)是以一組經(jīng)緯度來代表訂單生成的位置,我們要在這個位置顯示氣泡,webgl中是以一個三角形片元繪制各種圖形,矩形至少需要兩個三角形片元,6個頂點,除去可以共用的頂點,我們至少要知道圖(b)中0、1、2、3四個頂點的坐標。如圖所示,我們將經(jīng)緯度對應(yīng)的點作為矩形底邊的中點,結(jié)合圖片的寬高即可得到0、1、2、3四個點的坐標。


          圖5.3 著色器限制

          上圖展示了著色器的相關(guān)限制,與紋理相關(guān)的部分限制了片元著色器的紋理單元數(shù)量不超過16個,這就意味著如果我們將每個氣泡單獨繪制在一個canvas上,至多只能繪制16種類型的氣泡,當然這對于我們當前的業(yè)務(wù)場景也是夠用的,但是容量天花板相對較低。假設(shè)我們一個紋理單元繪制兩種氣泡,最多可繪制32種,以此類推。同時需要注意一下單個紋理單元的最大限制是16384,假設(shè)一張圖片的大小是200*200,那么一個紋理單元最多容納6561張圖片。如果我們繪制的范圍超過該限制,即要添加新的紋理單元,所以要注意邊界判斷。


          圖5.4 紋理繪制示意圖

          在繪制紋理畫布時,我們采用列主序的方式,maxTextureSize對應(yīng)的就是前面提到的單個紋理單元的最大限制,height代表每張圖片的高度。首先計算一列最多容納的圖片數(shù),然后根據(jù)總圖片數(shù)picNum得到紋理單元的行數(shù)heightNum和列數(shù)widthNum,根據(jù)以上信息,我們就可以精確的得到第m種氣泡類型的第n幀圖片在紋理畫布上的第幾行第幾列,從而映射到氣泡所在的位置。


          3.氣泡運動

          我們將氣泡運動分為兩類,一類是需要重復運動的,一類是只運動一次的。這兩種情況下我們都會記錄當前氣泡運動到哪一幀,區(qū)別是重復運動的氣泡會從第一幀到最后一幀不斷重復循環(huán),因為氣泡不會消失,所以重復運動適合于數(shù)據(jù)基本保持固定、不會隨時間累加的場景。對于只運動一次的場景,除了記錄氣泡的當前運動的幀數(shù)showIndex,我們會額外設(shè)定個參數(shù)iconStartIndex記錄當前動畫從第幾個氣泡開始執(zhí)行。假設(shè)當前數(shù)據(jù)中總共有100個氣泡的信息,每次渲染時將所有氣泡的showIndex加1并與總幀數(shù)frameNum比較,如果第n個氣泡的值大于等于frameNum,則代表該氣泡動畫結(jié)束,且在該氣泡之前的所有氣泡動畫也已結(jié)束,所以將n賦值給iconStartIndex,下次render時就會從第n+1個氣泡的動畫開始處理,而前n個氣泡因為動畫已結(jié)束所以忽略。

          4.數(shù)據(jù)更新

          為了保證數(shù)據(jù)的穩(wěn)定性、減少氣泡數(shù)據(jù)的請求次數(shù),同時保持頁面的豐盈性,我們采用了數(shù)據(jù)緩沖區(qū),運行條件為該請求至少有一次是請求成功的。初始化時我們會備份三份數(shù)據(jù),利用writeIndex和readIndex記錄當前寫入緩沖區(qū)與讀取緩沖區(qū)數(shù)據(jù)的位置。數(shù)據(jù)更新時,請求回來的新數(shù)據(jù)會根據(jù)writeIndex依次取代對應(yīng)位置的備份數(shù)據(jù)。同時為了讓頁面上的氣泡效果更豐滿,我們每隔幾秒鐘會從緩沖區(qū)中根據(jù)readIndex讀取對應(yīng)數(shù)據(jù)替換輪播的氣泡數(shù)據(jù)。


          6. 
          熱力
          熱力圖展示了以此時刻開始,過去一段時間內(nèi)北京城的訂單分布情況。我們將獲取到的訂單數(shù)據(jù)生成熱力圖并映射到建筑頂層,更加直觀準確且及時的展示了全城訂單分布。目前熱力圖可配置項包括:

          • 時間范圍

            可以展示過去一分鐘、十分鐘、一小時等任意時間間隔內(nèi)的熱力情況。

          • 半徑大小

            可以以任意距離為單位,該范圍內(nèi)的訂單數(shù)對應(yīng)的色階上的顏色即為該范圍的熱力圖顏色。
          • 色階

            熱力圖的顏色是可調(diào)的。


          1.生成熱力圖

          我們使用了現(xiàn)有的熱力圖庫heatmap.js,使用起來也很簡單(官網(wǎng)首頁)。在此基礎(chǔ)上,進行了二次開發(fā)使其更加適用于我們的大屏場景。下面簡單介紹一下生成熱力圖的原理。

          1)點模版

          每個熱力圖數(shù)據(jù)點代表一個訂單,會對應(yīng)的生成如上圖所示的點模版。根據(jù)之前配置的半徑大小,會在灰度圖上生成一個對應(yīng)大小的漸變圓,根據(jù)可配置的模糊因子,可使圓點帶有模糊效果。

          2)灰度(透明度)疊加

          圖6.1 灰度圖

          rgb通道是無法線性疊加呈現(xiàn)效果的,但是透明度是近似線性的。根據(jù)第一步生成的數(shù)據(jù)點模版的比率,對應(yīng)于透明度的值alpha,我們在canvas(shadowCtx)上繪制一個數(shù)據(jù)點,他們的透明度是可以疊加的,值越大,越不透明。

          3)線性色譜

          根據(jù)gradient生成自定義色譜,用于下一步著色。

          4)著色


          圖6.2 熱力圖


          最后,透明度的疊加值映射到線性色譜,取線性色譜中的顏色為canvas上色就得到上圖所示的最終的熱力圖。


          2.映射熱力圖


          最初的方案是將整個建筑都映射上熱力的顏色,但是在我們的項目中,熱力圖的透明度從俯視角度開始是逐漸遞減的,當鏡頭拉近到可以看清建筑時熱力圖已經(jīng)消失,所以最后我們優(yōu)化了之前的方案,只將熱力顏色映射到了建筑的上表面。


          3.調(diào)試工具



          圖6.3 熱力圖調(diào)試工具

          在調(diào)試過程中,因為燈光和屏幕的分辨率等因素導致最終在大屏上展示的效果和在臺式機上的效果還是有一些差別的,加之設(shè)計師側(cè)也不方便準確的描述差異,例如紅色淡一些這些想象空間很大的描述,所以溝通之后開放了上圖所示的半徑和四種顏色的調(diào)試接口,方便設(shè)計師現(xiàn)場實時查看熱力圖的改動效果。


          為了加快調(diào)參時熱力圖的響應(yīng),同時減少計算量,我們對原本的熱力圖代碼做了一些修改。原來代碼里和重繪有關(guān)的方法是repaint方法,但是調(diào)用該方法觸發(fā)的操作是全部重新繪制,包括灰度圖、線性色譜和熱力圖等。當我們只改變顏色時,是線性色譜發(fā)生改變從而著色時影響最終的熱力圖,灰度圖并沒有改變,所以我們添加了recolor方法,調(diào)試顏色時不會重繪灰度圖。但是,如果改變了半徑范圍,還是需要調(diào)用原本的repaint方法,全部重新繪制。


          4.參數(shù)解讀


          現(xiàn)在熱力圖已經(jīng)繪制完成并映射到建筑上,那么熱力圖的代表的具體含義是什么呢?例如多大距離范圍內(nèi)到達多少單才能顯示為圖中的紅色,代表高訂單區(qū)呢?


          我們根據(jù)屏幕的分辨率以及展示的地圖范圍,計算出1px對應(yīng)約47.6390m。參數(shù)radius的值對應(yīng)的就是像素數(shù),所以我們可以根據(jù)radius的值來確定是以多大距離作為統(tǒng)計單位,例如radius=10.5對應(yīng)的約是500m。


          圖6.4 訂單統(tǒng)計示意圖


          假設(shè)以方圓R范圍作為統(tǒng)計單位,如果這個圓都顯示紅色,那么其透明度的疊加值要為1。點模版是一個漸變圓,圓心處透明度為1代表不透明,邊緣處透明度為0代表完全透明。此外還有最大訂單量設(shè)定的影響,假設(shè)我們將最大訂單量設(shè)為MAX=10,那么每個點模版透明度的最大值即為0.1,所以點模版的透明度是從0~1/MAX變化的,不是從0~1變化的。設(shè)方圓R范圍內(nèi)的累計透明度為1所需要的訂單量為x單,則有:


          解得 x=3MAX,這意味著如果我們在程序中設(shè)定MAX的值來約束點模版透明度變化的范圍,那么至少需要3MAX數(shù)量的訂單才能是對應(yīng)的范圍展示為透明度為1對應(yīng)的紅色高訂單區(qū)。(注:此方法是一個大概的計算,會存在一定程度上的偏差。)


          7. 

          飛線

          (a)                  (b)
          圖7.1 飛線.gif
                                                                      

          上圖展示的是可視化大屏中飛線使用的兩種場景,左邊是直線,右邊是曲線。這兩種類型也是飛線最常使用的兩種情況,實現(xiàn)起來并無很大差異,在開發(fā)過程中遇到的問題為飛線取點和繪制性能方面,下面簡單說下。 

          1.直線

          直線飛線的實現(xiàn)采用了較為常用的實現(xiàn)方式,為每條飛線創(chuàng)建一個sprite,通過控制sprite的scale、opacity和自定義的height等參數(shù)來控制飛線的長度、透明圖和飛行高度。與常規(guī)使用不同的是,我們需要添加很多判定條件,因為飛線從不同的地方飛出,其長度和最大飛行高度是有限制的。如果我們采取統(tǒng)一標準,則會出現(xiàn)上半部分區(qū)域飛線過長、飛行高度過高以至于飛出屏幕,又或者下半部分區(qū)域飛線過短、飛行高度過低以至于存在感較低。最后我們采取來分段式限制,分為三個區(qū)域,上方飛線對應(yīng)的隨機長度和隨機飛行高度范圍較小,中間部分的始終,下方區(qū)域的范圍相對較大,經(jīng)過多次參數(shù)調(diào)整,最終達到了和諧的效果。


          2.曲線

          1)取點

          曲線是由一系列的點連接組成,若想得到平滑的曲線,首先需要得到能生成我們想要的曲線的點。這里我們每條曲線是由100個點連接而成,那么如何得到這100個點呢?如果只是想在地球上兩點之間生成一條曲線這本身不是難事,但是為了美觀性,我們想要達到每條曲線的最高點距離地球的距離都是相同的,這樣在地球轉(zhuǎn)動或者各種視角下,視覺體驗上才會比較好。為了實現(xiàn)這點,我們做了以下計算,代表的是飛線起點向量和終點向量之間的夾角:

          ① 

          此時我們使用三維三次貝塞爾曲線,根據(jù)起點startPoint、第一個控制點p1、第二個控制點p2、終點endPoint的坐標確定曲線,并利用其提供的getPoints方法得到曲線上的100個點用于繪制。

          ② 

          當角度大于30度時,我們發(fā)現(xiàn)得到的曲線兩端變化較大,中間很長一部分幾乎都是平緩的,整條曲線的彎曲變化不是特別順滑。所以為了解決這種情況,我們對 的曲線采用了不同于三維三次貝塞爾曲線的方法,使用的是CatmullRomCurve3,即使用Catmull-Rom算法, 從一系列的點創(chuàng)建一條平滑的三維樣條曲線,不僅限于兩個控制點。我們采取的方案是根據(jù)起點和終點的經(jīng)緯度差值、地球的半徑和曲線最高點對應(yīng)的半徑進行插值計算,得到20個插值點的坐標,將這20個點傳入CatmullRomCurve3中生成曲線,并利用其提供的getPoints方法得到曲線上的100個點用于繪制。

          2)紋理映射

          上一步我們獲取的100個點是描繪一條完整曲線的全部點,但是從圖7.1(b)中可以看出,飛線在飛的過程中展示的是完整的紋理,但是飛線長度只占總長度的1/3。如果按照正常的映射的話應(yīng)該是完整的紋理對應(yīng)完整的曲線,所以在飛的過程中應(yīng)該只展示對應(yīng)的紋理部分。那么如何實現(xiàn)動圖中的效果呢?

          第一反應(yīng)就是改變頂點坐標,例如第一次render第0~30個點的范圍,第二次render第10~40個點的范圍,以此類推。效果確實是實現(xiàn)了,但是性能上有問題,由于頻繁的更換頂點導致了頁面的卡頓。

          想到的第二種方案就是100個點全部繪制,在render的過程中z不斷變換頂點對應(yīng)的uv坐標。例如開始是第0~30個對應(yīng)的uv坐標從0~1,下一次是第10~40個點對應(yīng)的uv坐標從0~1,以此類推可以滿足1/3長度的飛線展示完整的紋理,且不斷移動。在這里還有個優(yōu)化,一開始是在每次render的時候去遍歷uv數(shù)組更改每個點對應(yīng)的值,但是遍歷數(shù)組也是耗性能的,特別是當數(shù)據(jù)量大的時候,后來是將一個常數(shù)傳進了shader中用于計算每個點此時的uv坐標,每次render都會改變這個數(shù),同時同步到shader中。


          8. 
          散點
          平時我們用靜態(tài)的散點比較多,這種實現(xiàn)很簡單。但當展示的信息量不是很豐滿時就顯得頁面有些空洞,這時候就需要添加動態(tài)呼吸的散點,讓頁面看起來更熱鬧。我們在散點大小變化的過程中對應(yīng)的改變其透明度,產(chǎn)生漸隱漸現(xiàn)的呼吸效果,同時我們可以隨機點出現(xiàn)的位置,讓效果看起來更靈動。


          9. 
          結(jié)語
          以上就是在全業(yè)務(wù)之城開發(fā)過程中的一些總結(jié)和思考,日后在不斷開發(fā)數(shù)據(jù)可視化大屏的同時,我們也會加速沉淀通用組件庫和搭建平臺。
          ·················END·················

          推薦閱讀

          1. 我在字節(jié)做了哪些事

          2. 寫給所有數(shù)據(jù)人。

          3. 從留存率業(yè)務(wù)案例談0-1的數(shù)據(jù)指標體系

          4. 數(shù)據(jù)分析師的一周

          5. 超級菜鳥如何入門數(shù)據(jù)分析?


          歡迎長按掃碼關(guān)注「數(shù)據(jù)管道」

          瀏覽 83
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99re99爱视频 | 岛国免费AV | 爱搞搞电影网 | 日本乱伦视频 | 日韩一二三|