在做可视化的时候,我们经常需要使用颜色渐变的线来表达某个方向或者某条路径上的温度、降雨量等指标的变化趋势,通常这个指标并不是均匀分布的,因此线颜色的渐变也并不是均匀的,我们需要使用颜色表达出值的波动。canvas为我们提供了强大的绘图能力,接下来我们学习一下利用canvas提供的图形接口来显示这种颜色渐变线的效果。
使用canvas绘制颜色渐变的线
首先我们先使用canvas绘制一条渐变的线,代码如下:
<canvas id="canvas" width="800" height="600" style="background: #cccccc;"></canvas>
<script type="text/javascript">
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// 创建一个渐变,参数为:开始x坐标、开始y坐标、结束x坐标、结束y坐标、
var grd=ctx.createLinearGradient(0, 0, 800, 0);
// 为渐变添加颜色,参数1表示渐变开始和结束之间的位置(用0至1的占比表示),参数2位颜色
grd.addColorStop(0,'yellow');
grd.addColorStop(0.5,'red');
grd.addColorStop(1,'blue');
// 将渐变赋值给线的样式
ctx.strokeStyle=grd;
// 设置线的宽度
ctx.lineWidth = 10;
// 绘制线
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(700, 500);
ctx.stroke();
</script>
通过下面绘制的这条渐变的线可以看出,线的渐变的颜色只是将配置的渐变区域的颜色根据线的坐标直接映射到了线上。但是我们需要的渐变的线并不是这样简单的映射,因为我们绘制的线上的每一个点都是有值的,并且每一个值对应不同的颜色。
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(100, 500);
ctx.lineTo(700, 500);
ctx.stroke();
映射值和颜色
前面我们学会了使用canvas去绘制一条渐变的线,但是这个渐变并不能完全的满足我们的需求。在实际的应用中我们拿到的每一点的数据通常是由坐标和该位置点的值组成的,x、y分别对应画布上的坐标,value表示该点的值。
const data = [{
x: 10,
y: 10,
value: 34
}, {
x: 50,
y: 15,
value: 134
}, {
x: 100,
y: 30,
value: 298
}, {
x: 150,
y: 60,
value: 404
}, {
x: 200,
y: 100,
value: 734
}, …………]
定义值的最大值和最小值
const MAX_VALUE = 1000;
const MIN_VALUE = 0;
映射值的颜色,当值为1000时使用'#ff0000'红色,值为最小值0时颜色为 '#00ff00',顾名思义当值为500时颜色为'#ffff00'黄色
const colors = {
0: '#00ff00',
0.5: '#ffff00',
1: '#ff0000'
}
那么如何将数据的value值和颜色映射到一起呐?当然你可以使用一些第三方插件来完成这个工作,这里我们介绍一种使用canvas来实现value—color映射对方法。首先我们创建一个宽度为256px高度为1px的canvas画布和一个线性渐变,将配置的colors添加到渐变中,并使用该渐变填充一个宽度256px高度1px的矩形,最后利用canvas的getImageData方法获取canvas内256*1范围的数据,得到的数组中存储的就是根据配置的颜色内插出来的颜色数组(具体结果参考getImageData方法)。
const palette = (function (palette) {
let canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
gradient = ctx.createLinearGradient(0, 0, 256, 0);
canvas.width = 256;
canvas.height = 1;
for (let i in palette) {
gradient.addColorStop(i, palette[i]);
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 1);
return ctx.getImageData(0, 0, 256, 1).data;
})(colors);
根据值获得获得每个值对应的颜色。
function getRGBForValue(value) {
let valueRelative = Math.min(Math.max((value - MIN_VALUE) / (MAX_VALUE - MIN_VALUE), 0), 1);
// 计算value的颜色索引
let paletteIndex = Math.floor(valueRelative * 256) * 4;
return [palette[paletteIndex], palette[paletteIndex + 1], palette[paletteIndex + 2]];
}
绘制渐变的线
至此,我们已经会绘制渐变线和根据值获取其对应的颜色了,接下来我们就可以根据data来绘制一条根据数据来渐变的线了。
ctx.lineCap = 'round'; // 配置圆形的结束线帽,保证线在转折处能够紧密结合
for (let index = 0; index < data.length - 1; index++) {
const startPoint = data[index];
const endPoint = data[index + 1];
let gradient = ctx.createLinearGradient(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
// 获取开始位置的颜色和结束位置的颜色
let gradientStartRGB = getRGBForValue(startPoint.value);
let gradientEndRGB = getRGBForValue(endPoint.value);
// 根据颜色配置渐变
gradient.addColorStop(0, 'rgb(' + gradientStartRGB.join(',') + ')');
gradient.addColorStop(1, 'rgb(' + gradientEndRGB.join(',') + ')');
// 绘制线
ctx.strokeStyle = gradient;
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(startPoint.x, startPoint.y);
ctx.lineTo(endPoint.x, endPoint.y);
ctx.stroke();
}
参考:
https://iosphere.github.io/Leaflet.hotline/demo/