近几年来,由于信息技术的发展,大数据成为了这个时代的代名词之一,“数据可视化”风靡一时。得益于HTML5提供的新标签“canvas”,Web端也能分“数据可视化”一杯羹。
随着越来越多的可视化方案和需求,需要解决问题也越来越多。当我们在Web端遇到重叠路径可视化渲染问题时,我们应该怎么做呢?
思路
按照数学的思路来说,对于上图所示的重叠路径渲染,我们应当计算出重叠的区域及重叠的次数,然后再分别进行渲染,就能得到上图。
但是,实践发现,在Web端这是一件极其复杂和困难的事情,我们不得不另寻他法。既然来硬的不行,就只能变通变通,另辟蹊径,做其他的尝试。
Canvas元素是一个位图,是基于像素的,在绘制图形时,当图形重叠时,其颜色通道会按照一定的规则进行叠加运算,如下图所示。
这就是我们的切入点,我们只需要根据叠加规则进行颜色再填充就能实现重叠路径的颜色区分渲染。
基于Canvas 2D
在Canvas2D 中,我们只需要设置填充颜色带透明度,绘制后获取ImageData,对ImageData进行修改,再重新渲染即可。
1.创建离屏canvas 设置透明度,进行渲染
const offscreenCanvas = new OffscreenCanvas(width, height); // 离屏canvas
const ctx = offscreenCanvas.getContext('2d');
ctx.fillStyle = 'rgba(0, 255, 0, 0.1)';
// 绘制内容
2.获取ImageData
const imageData = ctx.getImageData(0, 0, width, height); // 获取ImageData
3.通过imageData 计算遍数并改变颜色
const base = 0.1; // 上面第一步设置的填充颜色透明度
const data = imageData.data;
for(let i = 3; i < data.length; i++) {
const a = data[i];
const time = Math.log(1-a) / Math.log(1 - base); // 重叠次数
// 根据重叠次数更改颜色
}
return new ImageData(data, imageData.width, imageData.height);
// 返回imageData
4.再渲染上屏
ctx.putImageData(imageData, 0, 0); // 此处ctx为屏幕上canvas的上下文。
这样我们就可以使用Canvas2D API 在canvas 中绘制出重叠区域区分颜色的带状图。但是我们要注意一点,使用Path渲染时,一次渲染会被视为一个整体图案,透明度是不会叠加的。我们需要分批次渲染多边形,才能实现透明度叠加。
这种方法适合数据量不多且不存在高刷新的情况下,因为绘制数据、获取ImageData 及编辑ImageData需要占用很多的时间。
那还有没有更好的办法呢?Canvas2D 性能不满足的东西,就试着交给WebGL吧。
基于WebGL
WebGL是HTML5提供的基于canvas的像素栅格化的API,与canvas2D相比,它不再将绘制全交给CPU,而是由GPU进行图元绘制,在绘制性能上,有很大的提升。
原生WebGL使用相对比较复杂,在这里我们就使用Pixi.js进行搭建。
1.初始化
const app = new PIXI.Application({
width: 1920, height: 929,
antialias: true, // default: false 反锯齿
transparent: true, // default: false 透明度
resolution: 1 // default: 1 分辨率
});
const container = new PIXI.Container();
2.设置滤镜,用于改变颜色
const fragment = `
arying vec2 vTextureCoord;
uniform sampler2D uSampler;
void main(void)
{
vec4 currentColor = texture2D(uSampler, vTextureCoord);
if(currentColor.a == 0.0) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
} else {
highp float t = 1.0 - currentColor.a;
highp float l = log(t);
float temp = l / log(0.99);
float f = fract(temp);
int colorIndex = int(temp) - 1;
if(f >= 0.5) {
colorIndex += 1;
}
// 大致的遍数 colorIndex;
// getColor 为你设置颜色的方法;
gl_FragColor = getColor(colorIndex);
}}
`
const filter = new PIXI.Filter(null, fragment);
app.stage.filters = [filter];
3.绘制内容
const line = new PIXI.Graphics();
line.lineStyle(60, 0x00FF00, 0.1);
line.moveTo(x, y);
line.lineTo(x1, y1);
.......
line.x = 0;
line.y = 0;
container.addChild(line);
这样就可以用WebGL区分颜色绘制出重叠路径了。
但是这只是个大概值,原因是因为精度的问题,随着重叠的次数的增加,误差会越来越大。
透明度是一个0-1的值,在默认情况下,透明度叠加时,是按照 (1 - (1 - 透明度)的重叠次数次方)的规律叠加。
这个值的差异会越来越小,渲染时又会丢失部分精度,这样就影响到了我们获取正确的重叠次数。
优化计算规则
我们知道了透明度叠加的问题,那有什么更好的办法吗?
我们还是离不开颜色,除了透明度外,还有单个颜色通道的叠加。
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE);
当我们将WebGL混合像素算法的源混合因子和源目标合因子都指定为gl.ONE,单个颜色通道的颜色就会进行相加。我们指定颜色为 vec4(0.0, 1.0 / 255, 0, 1), 那么我们在着色器中可以这样写。
highp vec4 currentColor = texture2D(u_sampler, v_texCoord);
if(currentColor.g == 0.0) {
gl_FragColor = u_backGround;
} else {
// 颜色叠加 很准 最大限制255
int colorIndex = int(currentColor.g * 255.0);
gl_FragColor = getColor(colorIndex - 1);
}
这样绿色颜色通道就会随着叠加次数由0~1, 通过运算就能得到0~255的值, 就能保证在255次重叠下渲染的精确,至于更多次数,那就得另寻他法了。