canvas渲染热力图的一种方式

栏目: 后端 · 发布时间: 5年前

内容简介:今天早上看了下如果要绘制一个点的热力图,可以简单是的使用我们仔细观察下热力图,他其实就是一些颜色的渐变产生的效果,中间部分颜色深一点,外围浅一点,我们实际上就是根据权重的大小来着色。比如我们在[80, 80]的地方有一个点,像半径10的周围辐射,我们把重心的权重设为100,最外围设为10,我们很容易想到,使用一个单色绘制。最方便就是使用灰色,只需要使用透明度就可,其像素点的rgb值都是0,这样的数据就方便处理,如下图。

今天早上看了下 heatMap.js 的源码,了解了他是如何绘制热力图的,这里我们抛开其数据处理的部分,聚焦热力图的绘制。

如果要绘制一个点的热力图,可以简单是的使用 createRadialGradient 来实现,但是如果两个点的热力图发生了重叠,重叠部分当然不是简单的覆盖。这种情况下我们当然可以使用像素级的操作,结合两个点的热力图通过复杂的计算得到覆盖之后的热力图,但显然过于复杂。

我们仔细观察下热力图,他其实就是一些颜色的渐变产生的效果,中间部分颜色深一点,外围浅一点,我们实际上就是根据权重的大小来着色。比如我们在[80, 80]的地方有一个点,像半径10的周围辐射,我们把重心的权重设为100,最外围设为10,我们很容易想到,使用一个单色绘制。最方便就是使用灰色,只需要使用透明度就可,其像素点的rgb值都是0,这样的数据就方便处理,如下图。

canvas渲染热力图的一种方式

所以步骤就是先使用这种灰度先绘制到一个 canvas 上,其每一个点的rgba都是 (0, 0, 0, 0)(0, 0, 0, 255) 之间。现在就可以根据其alpha值将其着色。现在有一个渐变色卡如下,其对应关系就是alpha的值为0,对应色卡的左边,255对应右边。

canvas渲染热力图的一种方式

一种简单的方式就是使用渐变色绘制一个宽为256的canvas,取得这256个点的颜色,然后与canvas进行一一对应。比如,我们的主canvas中某个像素点的alpha值为100,那么就将该店的颜色修改为色卡中第100(程序员计数)个点的颜色。

具体实现过程如下:

getImageData

注:1. 每一个点根据值得大小设置颜色深度可以根据值得大小修改相应的 globalAlpha 。 2. 灰度canvas的绘制也不一定必须的绘制到主canvas,也可以使用离屏canvas,最后一步在讲结果绘制到主canvas(heatMap.js就是如此)。 3. 灰度数据可以使用Uint8ClampedArray来运算,不一定非得画出灰色的canvas来获取数据,计算并不复杂。

思路就是如此,下面就是一个简单的实现方式。

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渲染热力图的一种方式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

An Introduction to Genetic Algorithms

An Introduction to Genetic Algorithms

Melanie Mitchell / MIT Press / 1998-2-6 / USD 45.00

Genetic algorithms have been used in science and engineering as adaptive algorithms for solving practical problems and as computational models of natural evolutionary systems. This brief, accessible i......一起来看看 《An Introduction to Genetic Algorithms》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具