内容简介:今天早上看了下如果要绘制一个点的热力图,可以简单是的使用我们仔细观察下热力图,他其实就是一些颜色的渐变产生的效果,中间部分颜色深一点,外围浅一点,我们实际上就是根据权重的大小来着色。比如我们在[80, 80]的地方有一个点,像半径10的周围辐射,我们把重心的权重设为100,最外围设为10,我们很容易想到,使用一个单色绘制。最方便就是使用灰色,只需要使用透明度就可,其像素点的rgb值都是0,这样的数据就方便处理,如下图。
今天早上看了下 heatMap.js 的源码,了解了他是如何绘制热力图的,这里我们抛开其数据处理的部分,聚焦热力图的绘制。
如果要绘制一个点的热力图,可以简单是的使用 createRadialGradient
来实现,但是如果两个点的热力图发生了重叠,重叠部分当然不是简单的覆盖。这种情况下我们当然可以使用像素级的操作,结合两个点的热力图通过复杂的计算得到覆盖之后的热力图,但显然过于复杂。
我们仔细观察下热力图,他其实就是一些颜色的渐变产生的效果,中间部分颜色深一点,外围浅一点,我们实际上就是根据权重的大小来着色。比如我们在[80, 80]的地方有一个点,像半径10的周围辐射,我们把重心的权重设为100,最外围设为10,我们很容易想到,使用一个单色绘制。最方便就是使用灰色,只需要使用透明度就可,其像素点的rgb值都是0,这样的数据就方便处理,如下图。
所以步骤就是先使用这种灰度先绘制到一个 canvas
上,其每一个点的rgba都是 (0, 0, 0, 0)
到 (0, 0, 0, 255)
之间。现在就可以根据其alpha值将其着色。现在有一个渐变色卡如下,其对应关系就是alpha的值为0,对应色卡的左边,255对应右边。
一种简单的方式就是使用渐变色绘制一个宽为256的canvas,取得这256个点的颜色,然后与canvas进行一一对应。比如,我们的主canvas中某个像素点的alpha值为100,那么就将该店的颜色修改为色卡中第100(程序员计数)个点的颜色。
具体实现过程如下:
getImageData
注:1. 每一个点根据值得大小设置颜色深度可以根据值得大小修改相应的 globalAlpha
。 2. 灰度canvas的绘制也不一定必须的绘制到主canvas,也可以使用离屏canvas,最后一步在讲结果绘制到主canvas(heatMap.js就是如此)。 3. 灰度数据可以使用Uint8ClampedArray来运算,不一定非得画出灰色的canvas来获取数据,计算并不复杂。
思路就是如此,下面就是一个简单的实现方式。
interface HeatMapConfig { gradient?: object; radius?: number; width?: number; height?: number; min?: number; max?: number; container: HTMLElement } interface PointData{ x: number; y: number; value: number; } class HeatMap { static defaultConfig = { gradient: { 0.3: "blue", 0.5: "lime", 0.7: "yellow", 1: "red" }, min: 0, max: 100, radius: 40, width: 400, height: 400 } private config: HeatMapConfig; private canvas = this.createCanvas(); private ctx = this.canvas.getContext('2d'); private data: PointData[] = []; constructor(config: HeatMapConfig) { this.initConfig(config); } private initConfig(config: HeatMapConfig) { if(!config.container) { throw Error('no container'); } this.config = { ...HeatMap.defaultConfig, ...config }; const {width, height} = this.config; this.canvas.width = width; this.canvas.height = height; this.config.container.appendChild(this.canvas); } initData(data: PointData[]) { this.data = data; this.render(); } private render() { this.renderAlpha(); this.putColor() } // 绘制alpha通道的圆 private renderAlpha(){ const shadowCanvas = this.createShadowTpl(); const {min, max} = this.config; for(let point of this.data) { const alpha = (point.value - min) / (max - min); this.ctx.globalAlpha = alpha; this.ctx.drawImage(shadowCanvas, point.x, point.y); } } // 为alpha通道的圆着色 private putColor() { const colorData = this.createColordata(); const imgData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); const {data} = imgData for(let i = 0; i < data.length; i++) { const value = data[i]; if(value) { data[i - 3] = colorData[4 * value]; data[i - 2] = colorData[4 * value + 1]; data[i - 1] = colorData[4 * value + 2]; } } this.ctx.putImageData(imgData, 0, 0); } private createCanvas(){ return document.createElement('canvas') } private createColordata(){ const cCanvas = this.createCanvas(); const cCtx = cCanvas.getContext('2d'); cCanvas.width = 256; cCanvas.height = 1; const tuple: [number, number, number, number] = [0, 0, cCanvas.width, cCanvas.height] const grd = cCtx.createLinearGradient(...tuple); const {gradient} = this.config; for(let key in gradient) { grd.addColorStop(parseFloat(key), gradient[key]); } cCtx.fillStyle = grd; cCtx.fillRect(0, 0, cCanvas.width, cCanvas.height); return cCtx.getImageData(...tuple).data; } /** * 离屏canvas绘制一个黑色(rgb都是0,方便处理)的alpha通道的圆 */ private createShadowTpl() { const tCanvas = this.createCanvas(); const tCtx = tCanvas.getContext('2d'); const blur = 0; const radius = this.config.radius; tCanvas.width = 2 * radius; tCanvas.height = 2 * radius; const grd = tCtx.createRadialGradient(radius, radius, blur, radius, radius, radius); grd.addColorStop(0, 'rgba(0,0,0,1)'); grd.addColorStop(1, 'rgba(0,0,0,0)'); tCtx.fillStyle = grd; tCtx.fillRect(0, 0, 2 * radius, 2 * radius); return tCanvas; } } const heatmap = new HeatMap({ container: document.body }); const data: PointData[] = []; for(var i = 0; i < 100; i++) { data.push({ x: Math.random() * 400, y : Math.random() * 400, value: Math.random() * 100 }) } heatmap.initData(data); 复制代码
以上所述就是小编给大家介绍的《canvas渲染热力图的一种方式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 每日一博 | 各种动态渲染 Element 方式的性能探究
- Java 社区平台 Sym 2.6.0 发布,增加帖子列表渲染方式
- Octane渲染入门-渲染设置图文版
- 通过分析 WPF 的渲染脏区优化渲染性能
- React 服务器端渲染和客户端渲染效果对比
- iOS渲染-将视频原始数据(RGB,YUV)渲染到屏幕上
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
点击的奥秘:运用说服心理术提升在线影响力(全彩)
Nathalie Nahai(娜塔莉.纳海) / 陈旭 / 电子工业出版社 / 2014-9-1 / 75.00元
用户的每一次点击,不管是在虚拟商店购物,还是在浏览企业网站,或是漫无目的地把玩手机,都蕴藏着基于心理学的无穷奥秘。《点击的奥秘:运用说服心理术提升在线影响力》作者为全球知名的网络心理学家,其在《点击的奥秘:运用说服心理术提升在线影响力》中将心理学、神经科学及行为经济学巧妙地结合在一起,挖掘和提炼出一套行之有效的网络用户引导策略——既涵盖在线说服最新研究动向,也包括最前沿的科技成果,以及其他诸多惊人......一起来看看 《点击的奥秘:运用说服心理术提升在线影响力(全彩)》 这本书的介绍吧!