日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > HTML >内容正文

HTML

基于 HTML5 Canvas 的 3D 热力云图效果

發(fā)布時(shí)間:2023/12/10 HTML 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于 HTML5 Canvas 的 3D 热力云图效果 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

  數(shù)據(jù)蘊(yùn)藏價(jià)值,但數(shù)據(jù)的價(jià)值需要用?IT?技術(shù)去發(fā)現(xiàn)、探索,可視化可以幫助人更好的去分析數(shù)據(jù),信息的質(zhì)量很大程度上依賴于其呈現(xiàn)方式。在數(shù)據(jù)分析上,熱力圖無(wú)疑是一種很好的方式。在很多行業(yè)中都有著廣泛的應(yīng)用。

  最近剛好項(xiàng)目中需要用到?3D?熱力圖的效果展示。網(wǎng)上搜了相關(guān)資料,發(fā)現(xiàn)大多數(shù)是?2D?效果或者偽?3D?的,而?3D?粒子效果對(duì)于性能上的體驗(yàn)不是很好,于是取巧寫(xiě)了個(gè)?3D?熱力圖的效果?。

  Demo?:?http://www.hightopo.com/demo/heatMap3D/

  部分效果圖:

?

應(yīng)用場(chǎng)景

  大樓內(nèi)的人員分布熱力圖。我們可以通過(guò)觀察到一個(gè)區(qū)域的顏色深淺來(lái)判斷該區(qū)域內(nèi)實(shí)時(shí)的人員流動(dòng)情況,知道哪個(gè)區(qū)域人多,哪個(gè)區(qū)域人少。該場(chǎng)景可適用于大樓內(nèi)的警務(wù)監(jiān)控,在發(fā)生突發(fā)事件時(shí)科學(xué)高效地制定分流疏導(dǎo)策略提供有力的幫助和支持,減少損失。亦可用于火險(xiǎn)預(yù)警,監(jiān)控區(qū)域?qū)崟r(shí)溫度。

  室內(nèi)設(shè)備溫度熱力圖。傳統(tǒng)的數(shù)據(jù)中心匯報(bào)方式枯燥單調(diào)、真實(shí)感不強(qiáng),互動(dòng)性差等,借助于?3D?熱力圖的可視化呈現(xiàn)方式,機(jī)房運(yùn)維管理人員可大大提高工作效率及降低工作失誤的可能性。

整體思路

  在場(chǎng)景反序列化之后,設(shè)置熱力圖的初始參數(shù),初始化后得到的熱力圖模型添加進(jìn)場(chǎng)景中,模擬?3D?熱力圖效果,最后再添加掃描、換膚、溫度提示等功能。

1.數(shù)據(jù)準(zhǔn)備

  在場(chǎng)景中畫(huà)出熱力圖的區(qū)域,如圖

  首先確定要生成熱力圖的區(qū)域 areaNode?,然后隨機(jī)生成?20??個(gè)點(diǎn)的信息,包含坐標(biāo)?position?(坐標(biāo)是相對(duì)紅色長(zhǎng)方體的某個(gè)頂點(diǎn)) 及熱力值?temperature?。

  以下是該部分的主要代碼:

function getTemplateList(areaNode, hot, num) {let heatRect = areaNode.getRect();let { width, height } = heatRect;let rackTall = areaNode.getTall();hot = hot + this.random(20);let templateList = [];for (let i = 0; i < num; i++) {templateList.push({position: {x: 0.2 * width + this.random(0.6 * width),y: 0.2 * height + this.random(0.6 * height),z: 0.1 * rackTall + this.random(0.8 * rackTall)},temperature: hot});}return templateList; } let heatMapArea_1 = dm.getDataByTag('heatMapArea_1'); let templateList_1 = this.getTemplateList(heatMapArea_1,70,20 );

2.初始化

  使用?ht-thermodynamic.js??插件來(lái)生成熱力圖。

?  發(fā)熱點(diǎn)的數(shù)據(jù)準(zhǔn)備好后,接著配置熱力圖的參數(shù),參數(shù)說(shuō)明如下。

// 默認(rèn)配置 let config = {hot: 45,min: 20,max: 55,size: 50,pointNum: 20,radius: 150,opacity: 0.05,colorConfig: {0: 'rgba(0,162,255,0.14)',0.2: 'rgba(48,255,183,0.60)',0.4: 'rgba(255,245,48,0.70)',0.6: 'rgba(255,73,18,0.90)',0.8: 'rgba(217,22,0,0.95)',1: 'rgb(179,0,0)'},colorStopFn: function (v, step) { return v * step * step }, }; // 獲取區(qū)域數(shù)據(jù) let rackTall = areaNode.getTall(); let heatRect = areaNode.getRect(); let { width, height } = heatRect; if (width === 0 || height === 0) return; // 熱力圖初始化 let thd = this.thd = new ht.thermodynamic.Thermodynamic3d(g3d, {// 熱力圖所占用的空間box: new ht.Math.Vector3(width, height, rackTall),// 配置溫度的最小值和最大值 min: config.min,max: config.max,// 每一片的渲染間隔interval: 40,// 為false時(shí),溫度區(qū)域交集時(shí)值不累加,取最高溫度remainMax: false,// 每一片的透明度opacity: config.opacity,// 顏色步進(jìn)colorStopFn: config.colorStopFn,// 顏色范圍 gradient: config.colorConfig }); 

3.加載熱力圖

  將第一步生成的發(fā)熱點(diǎn),設(shè)置?thd?的數(shù)據(jù)對(duì)象,調(diào)用?thd.createThermodynamicNode()?來(lái)生成熱力圖的?3D?圖元。設(shè)置其相關(guān)信息,將該圖元添加進(jìn)?3D?場(chǎng)景中。這樣一個(gè)簡(jiǎn)單的?3D?熱力圖就算完成了。

// 加載熱力圖 function loadThermodynamic(thd, areaNode, templateList, config) {thd.setData(templateList);// x,y,z面數(shù)let node = this.heatNode = thd.createThermodynamicNode(config.size, config.size, config.size);let p3 = areaNode.p3();node.setAnchorElevation(0);node.p3(p3);node.s({'interactive': true,'preventDefaultWhenInteractive': false,'3d.movable': false,"wf.visible": false});g3d.dm().add(node); }

  主體介紹完了,現(xiàn)在開(kāi)始講講該?demo?的幾個(gè)功能。

4.溫度提示

  因?yàn)樵?3D?場(chǎng)景中,我不好判斷當(dāng)前鼠標(biāo)坐標(biāo)(x,y,z),所以我將?tip?面板放在了?2D?圖紙上,將?2D?圖紙嵌在?3D?場(chǎng)景的上層。通過(guò)監(jiān)聽(tīng)?3D?場(chǎng)景中的?onMove?事件來(lái)控制?tip?面板的顯隱及值的變化。
  tip?顯隱控制:當(dāng)鼠標(biāo)移入進(jìn)熱力圖區(qū)域時(shí),tip?顯示,反之則隱藏。在這我遇到了個(gè)問(wèn)題,因?yàn)槲野殉藷崃D區(qū)塊以外的設(shè)置成不可交互的,當(dāng)鼠標(biāo)移出區(qū)域后,無(wú)法監(jiān)聽(tīng)到?onMove?事件,導(dǎo)致?bug,tip?面板始終存在著。我使用了?setTimeout?來(lái)解決這問(wèn)題,延時(shí)1s后自動(dòng)隱藏,但后來(lái)發(fā)現(xiàn)完全沒(méi)必要濫用 setTimeout ,只要監(jiān)聽(tīng)?onLeave?時(shí)隱藏?tip?就行了。
  tip?值控制:調(diào)用?ht-thermodynamic.js?的方法可以獲取到當(dāng)前鼠標(biāo)相對(duì)熱力圖區(qū)域的溫度值?thd.getHeatMapValue(e.event,'middle'),實(shí)時(shí)改變?tip?面板的?value?屬性 。
  代碼如下:

// 交互效果 g3d.mi(e => {if (e.kind === 'onMove') {let { clientX, clientY } = e.event;if (this.templateTip) {let value1 = this.thd1.getHeatMapValue(e.event, 'middle');let value2 = this.thd2.getHeatMapValue(e.event, 'middle');if (value1 || value1 === 0 || value2 || value2 === 0) {let position = g2d.getLogicalPoint({ x: clientX, y: clientY })this.templateTip.a('value', value1 || value2 || 0)let { width, height } = this.templateTip.getRect()this.templateTip.setPosition({ x: position.x + width / 2, y: position.y - height / 2 })}}} else if (kind === 'onLeave') {let tag = data.getTag()if (tag && tag.hasOwnProperty('hoverBlock') > -1) {this.g2d.getView().style.cursor = 'default';}this.templateTip && this.setVisible(this.templateTip, false)} })

5.掃描

  將第三步中的?thd.createThermodynamicNode() 替換。在生成熱力圖對(duì)象時(shí),不直接返回一個(gè)模型,而是選擇某一個(gè)方向進(jìn)行“切割”,將這一方向的長(zhǎng)度均分為 n 份,通過(guò) thd.getHeatMap()??方法來(lái)獲取每一片的熱成像。n 的值理論上可以取任意值,但為了渲染效果更好一點(diǎn),這里我取的是?50,不至于太多而導(dǎo)致首次渲染時(shí)間過(guò)長(zhǎng)。每切出一個(gè)面,我們就在熱力區(qū)域的相對(duì)位置上動(dòng)態(tài)創(chuàng)建一個(gè) ht.Node ,接著使用?ht.Default.setImage() 將切出來(lái)的面注冊(cè)成圖片,去設(shè)置成該 node 的貼圖(只需設(shè)置切割方向上的兩個(gè)面就行)。最后將所有的 node 添加進(jìn)?dataModel?(?ht 中承載?Data?數(shù)據(jù)的模型)。

  掃描功能,有兩種方案。第一種是在步驟?3?切割貼片時(shí),不去創(chuàng)建?n?個(gè)? node?,而是只創(chuàng)建一個(gè),然后動(dòng)態(tài)去設(shè)置該?node?的貼圖及坐標(biāo),模擬掃描效果;第二種依舊創(chuàng)建?n?個(gè)?node,然后全部隱藏,通過(guò)不同時(shí)刻來(lái)控制讓其中某一個(gè)節(jié)點(diǎn)顯示,模擬掃描功能。這里我采用了第二種,因?yàn)榈谝环N要去頻繁的修改多種屬性才能達(dá)到效果,第二種的話只要控制其 '3d.visible'。

  主要代碼如下:

let length; if (dir === 'z') {length = rackTall; } else if (dir === 'x') {length = width; } else if (dir === 'y') {length = height; } let size = config.size; for (let index = 0; index < size; index++) {// 熱力切圖間隔const offset = length / size;let timer = setTimeout(() => {let ctx = thd.getHeatMap(index * offset, dir, colorConfig);let floor = this.getHeatFloor(areaNode,dir,ctx,index,size,config);this.floors.push(floor);dm.add(floor);}, 0);this.timers.push(timer); } function start() {this.hide();this.anim = true;this.count = 0;let frames = this.floors.length;let params = {frames, // 動(dòng)畫(huà)幀數(shù)interval: 50, // 動(dòng)畫(huà)幀間隔毫秒數(shù)easing: t => {return t;},finishFunc: () => {if (this.anim) {this.start();}},action: (v, t) => {this.count++;this.show(this.count);}};this.scanning = ht.Default.startAnim(params); } function hide(index) {if (index || index === 0) {this.floors.forEach((i, j) => {if (index === j) {i.s('3d.visible', false);}else {i.s('3d.visible', true);}});}else {this.floors.forEach(i => {i.s('3d.visible', false);});} } function show(index) {if (index || index === 0) {this.floors.forEach((i, j) => {if (index === j) {i.s('3d.visible', true);}else {i.s('3d.visible', false);}});}else {this.floors.forEach(i => {i.s('3d.visible', true);});} }

第一種方式實(shí)現(xiàn)主要代碼:

getHeatFloor(node, dir, config) {let { width, height } = node.getRect();let rackTall = node.getTall();let s3 = [1, rackTall, height];let floor = new ht.Node();floor.setTag('hotspot');floor.setAnchor3d({x: 0.5,y: 0.5,z: 0.5});floor.s3(s3);floor.s({interactive: true,preventDefaultWhenInteractive: false,'3d.selectable': true,'3d.movable': false,'all.visible': false,[Top + '.visible']: true,[Top + '.opacity']: config.opacity,[Top + '.transparent']: true,[Top + '.reverse.flip']: true,[Top + '.color']: 'rgba(51,255,231,0.10)'});return floor } getHeatFloorInfo(node, dir, ctx, index, size, config) {let { width, height } = node.getRect();let rackTall = node.getTall();let point = node.getPosition3d();let part = 0;let p3, s3;let Top = 'top';if (!dir) {dir = 'z';}// 熱力圖的yz方向與ht的yz方向相反 dir=z代表的是豎直方向if (dir === 'x') {Top = 'left';part = (width / size) * index;p3 = [point[0] - width / 2 + part,point[1] + rackTall / 2,point[2]];// p3 = [point[0] + part, point[1], point[2]];s3 = [1, rackTall, height];}else if (dir === 'y') {Top = 'front';part = (height / size) * index;p3 = [point[0],point[1] + rackTall / 2,point[2] - height / 2 + part];s3 = [width, rackTall, 1];}else if (dir === 'z') {Top = 'top';part = (rackTall / size) * index;p3 = [point[0], point[1] + part, point[2]];s3 = [width, 1, height];}let heatName = this.generateUUID();ht.Default.setImage('heatMap' + heatName, ctx);this.heatFloorInfo.push({img: 'heatMap' + heatName,p3}) } show(index){let info = this.heatFloorInfo[index]this.floor.p3(info.p3)this.floor.s('3d.visible', true);this.floor.s('top.image', info.img);// 手動(dòng)刷新this.floor.iv(); }

6.換膚

  換膚的實(shí)現(xiàn)原理:根據(jù)不同的場(chǎng)景值去動(dòng)態(tài)修改?ht.graph3d.Graph3dView?的背景色及墻的顏色等。

  代碼:

function changeSkin() {let backgroundColor = this.g3d.dm().getBackground(),dark_bg = this.g3d.dm().getDataByTag('dark_skin'),light_bg = this.g3d.dm().getDataByTag('light_skin');if (backgroundColor !== 'rgb(255,255,255)') {this.g3d.dm().setBackground('rgb(255,255,255)');} else {this.g3d.dm().setBackground('rgb(0,0,0)');}dark_bg.s('2d.visible', !dark_bg.s('2d.visible'));dark_bg.s('3d.visible', !dark_bg.s('3d.visible'));light_bg.s('2d.visible', !light_bg.s('2d.visible'));light_bg.s('3d.visible', !light_bg.s('3d.visible'));}

?  本篇就介紹到了,目前?ht-thermodynamic.js?還處于測(cè)試階段,待到相對(duì)成熟后再更新該?demo?,有興趣了解更多關(guān)于?2D/3D?可視化的構(gòu)建,可翻閱其他文章的例子,HT?會(huì)給你很多不可思議的東西。

總結(jié)

以上是生活随笔為你收集整理的基于 HTML5 Canvas 的 3D 热力云图效果的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。