Heatmap热图经过众多数据点信息,汇聚成直观可视化颜色效果,热图已普遍被应用于气象预报、医疗成像、机房温度监控等行业,甚至应用于竞技体育领域的数据分析。html
已有众多文章分享了生成Heatmap热图原理,可参考《How to make heat maps》和《How to make heat maps in Flex》,本文将介绍基于HTML5技术的实现方式,主要基于Cavans和WebGL这两种HTML5的2D和3D技术的应用,先上最终例子实现的界面效果和操做视频:node
http://v.youku.com/v_show/id_XNzc5ODYxNjY4.html?firsttime=0
实现Heatmap的开源js库比较出名的就是 heatmapjs ,该框架发展了2年多,做者Patrick Wied最近决定在保持开源的基础上,提供有偿的商业支持服务,这是好事,地球上绝大部分开源项目做者搞个barely可用的初级版本后,就多年不见更新了,而真正能实际上线使用的产品哪有不须要持续完善、加强可扩展性以及提供特殊定制服务的,考虑到做者这两年已无偿投了这么多(Over the last 2 years, I devoted more than 500 hours of work to improving heatmap.js to make it a truly great library. ),但愿此文也能帮做者在国内起点宣传做用。git
heatmapjs 采用的Canvas的2D绘制方式实现,这种基于CPU的绘制方式对于几百几千的点还凑合,但若是须要实时运算成千上万节点效果的,仍是得依靠并发性更强大的GPU方式,采用HTML5的话只能是WebGL方案,还好Florian Boesch在《High Performance JS heatmaps》博客中提供了基于WebGL实现的heatmap方式,并将其开源在https://github.com/pyalot/webgl-heatmap 上,这两个开源库质量都还不错,一个基于Canvas实现,一个基于WebGL实现,后者性能高点,但须要支持WebGL的浏览器,heatmapjs 的文档例子比较全面,但二者接口都很是简单易学,代码也都就几百行,你彻底能够根据项目状况选择甚至进行代码改造优化。github
回到咱们要实现的例子,我将采用heatmapjs在内存中实时运算出热图,结合hightopo的HT for Web的3D引擎,以一堆节点连线关系的3D的网络拓扑图,其中节点表明热源,其越接近地面则地面温度越高,这样每一个节点的xz面坐标信息做为要传入给heatmapjs的点xy二维坐标信息,三维节点的elevation也就是y轴信息,则做为离地面的距离信息,距离越大转成要传入heatmapjs的value值越小,最后启动HT for Web的三维拓扑自动布局弹力算法,这样可直观的观察图元节点在不一样的空间位置动态变化时地板的温度热图变化效果。web
代码核心就在重载forceLayout.onRelaxed函数,在每次自动布局过程将全部热源节点的信息构建成heatmap须要的数据,同时经过ht.Default.setImage(‘hm-bottom’, heatmap._renderer.canvas);将热图的canvas注册成HT的图片,而floor的地板图元绑定了注册的’hm-bottom’图片,这样就实现了内存绘制canvas,而后经过HT for Web的3D引擎将Cavnas做为贴图信息动态呈现到3D场景的效果。算法
整个实现代码以下不到百行,你也能够采用https://github.com/pyalot/webgl-heatmap 的WebGL方式来实现,这样就是3D到2D再到3D的有趣过程,这就是HTML5技术可无缝融合各类方案的魅力!canvas
MAX = 500; WIDTH = 1024; HEIGHT = 512; function init() { dataModel = new ht.DataModel(); g3d = new ht.graph3d.Graph3dView(dataModel); g3d.getMoveMode = function(e){ return 'xyz'; }; view = g3d.getView(); view.className = 'main'; document.body.appendChild(view); window.addEventListener('resize', function (e) { g3d.invalidate(); }, false); heatmap = h337.create({ width: WIDTH, height: HEIGHT }); ht.Default.setImage('hm-bottom', heatmap._renderer.canvas); var floor = new ht.Node(); floor.s3(WIDTH, 1, HEIGHT); floor.s({ '3d.selectable': false, 'layoutable': false, 'all.visible': false, 'top.visible': true, 'top.image': 'hm-bottom', 'top.reverse.flip': true, 'bottom.visible': true, 'bottom.transparent': true, 'bottom.opacity': 0.5, 'bottom.reverse.flip': true }); dataModel.add(floor); var root = createNode(); for (var i = 0; i < 3; i++) { var iNode = createNode(); createEdge(root, iNode); for (var j = 0; j < 3; j++) { var jNode = createNode(); createEdge(iNode, jNode); } } forceLayout = new ht.layout.Force3dLayout(g3d); forceLayout.start(); forceLayout.onRelaxed = function(){ var points = []; dataModel.each(function(data){ if(data instanceof ht.Node && data !== floor){ var p3 = data.p3(); if(p3[1] > MAX){ p3[1] = MAX; data.setElevation(MAX); } else if(p3[1] < -MAX){ p3[1] = -MAX; data.setElevation(-MAX); } points.push({ x: p3[0] + WIDTH/2, y: p3[2] + HEIGHT/2, value: MAX - Math.abs(p3[1]) }); } }); heatmap.setData({data: points, min: 0, max: MAX}); }; } function createNode(){ var node = new ht.Node(); node.s({ 'shape3d': 'sphere', 'shape3d.color': '#E74C3C', 'shape3d.opacity': 0.8, 'shape3d.transparent': true, 'shape3d.reverse.cull': true }); node.s3(20, 20, 20); dataModel.add(node); return node; } function createEdge(sourceNode, targetNode){ var edge = new ht.Edge(sourceNode, targetNode); edge.s({ 'edge.width': 3, 'edge.offset': 10, 'shape3d': 'cylinder', 'shape3d.opacity': 0.7, 'shape3d.transparent': true, 'shape3d.reverse.cull': true }); dataModel.add(edge); return edge; }