可视化图表中的环形渐变实现
以 echarts 饼状图为例,实现环形渐变填充。
饼图是一种将数据大小映射到扇形角度的可视化图表。该模块以开始角度、结束角度作为输入,将数据中给出的值按比例映射到开始角度到结束角度这个区间内。
echarts 默认支持线性渐变与径向渐变,不支持环形渐变,如果要实现环形渐变,只能用线性渐变模拟实现类似环形渐变的效果。
备注
线性渐变(linear-gradient)就是沿着一根轴线(水平、垂直或某个角度)改变颜色,从起点到终点颜色进行顺序渐变。
径向渐变指从一个中心开始向远处渐变的效果,可以指定渐变的中心、形状(圆形或椭圆形)、大小。
CSS 的background
也是支持这两种渐变方式。
上面环形渐变的效果该如何实现呢?
初步数据处理
const data = [
{ name: 'data1', value: 100 },
{ name: 'data2', value: 40 },
{ name: 'data3', value: 60 },
];
// 计算每部分比例,以及开始角度,结束角度
const dataTotal = data.reduce((prev, curr) => prev + curr.value, 0);
data.forEach((item, idx) => {
item.percent = item.value / dataTotal;
item.angle = item.percent * Math.PI * 2; // 弧度制的角度
if (idx == 0) {
item.startAngle = 0;
item.endAngle = item.angle;
} else {
item.startAngle = data[idx - 1].startAngle + data[idx - 1].angle;
item.endAngle = item.startAngle + item.angle;
}
});
// 颜色列表
const color = [
{
r: 1,
g: 193,
b: 178,
},
{
r: 49,
g: 205,
b: 83,
},
{
r: 255,
g: 214,
b: 0,
},
];
具体渐变方向计算
/**
* 线性渐变起止方向的计算方法
* @param {*} startArc 开始角度
* @param {*} endArc 结束角度
* @returns 四个坐标 x,y,x2,y2
*/
function getCoordinates(startArc, endArc) {
// 这里计算扇形最外层的x,y坐标
const position = [
Math.sin(startArc),
-Math.cos(startArc),
Math.sin(endArc),
-Math.cos(endArc),
];
// 这里求得了最外层两个顶点坐标的差值。
const dx = position[2] - position[0];
const dy = position[3] - position[1];
// 这里在根据两点坐标的差值计算x,y,x2,y2
return getLocation(dx, dy);
}
function getLocation(dx, dy) {
const tanV = dx / dy;
// 这里是在计算按照横向渐变还是按照纵向渐变。
const directSign = Math.abs(tanV) < 1;
const t = directSign ? tanV : 1 / tanV;
const sign1 = t > 0 ? 1 : -1;
const sign2 = dx > 0 ? 1 : -1;
const sign = directSign ? sign1 * sign2 : sign2;
// 把整个圆形的坐标映射到了[0-1]之间0.5,0.5即为圆心坐标。
const group1 = [0.5 - (sign * t) / 2, 0.5 + (sign * t) / 2];
// 这里给纵向渐变还是横向渐变赋值、即group中的第三项和第四项的值
const group2 = sign > 0 ? [0, 1] : [1, 0];
const group = [...group1, ...group2];
const keys = directSign ? ['x', 'x2', 'y', 'y2'] : ['y', 'y2', 'x', 'x2'];
const res = {};
keys.forEach((k, idx) => {
res[k] = group[idx];
});
return res;
}
/**
* 给数据写入 样式(线性渐变)
*
* @param {*} data 数据
* @param {*} colorlist 颜色列表
* @param {*} startOpacity 开始颜色的透明度
* @param {*} endOpacity 结束颜色透的明度
* @returns 带样式的数据
*/
function setGradientColorInItemStyle(
data,
colorlist,
startOpacity = 1,
endOpacity = 0,
) {
for (let i = 0; i < data.length; i++) {
const color = colorlist[i];
const startAngle = data[i].startAngle;
const endAngle = data[i].endAngle;
// 这里计算了 线性渐变的起止方向
// @ts-ignore
const coordinates = getCoordinates(startAngle, endAngle);
data[i].itemStyle = {
color: {
...coordinates,
type: 'linear',
global: false,
colorStops: [
{
offset: 0,
color: `rgba(${color.r}, ${color.g}, ${color.b}, ${startOpacity})`,
},
{
offset: 0.91,
color: `rgba(${color.r}, ${color.g}, ${color.b}, ${endOpacity})`,
},
],
},
};
}
return data;
}