效果如下:
具体方案:
使用两个pie的series,通过z控制层级遮盖住底层的pie。
控制底层radius外环略微大于表层的pie的radius来模拟出边框。
series: [
{
name: "Nightingale Chart",
type: "pie",
z: 20,
radius: ["0%", "70%"],
center: ["50%", "50%"],
roseType: "radius",
emphasis: { disabled: true },
showEmptyCircle: false,
label: {
show: true,
position: "outside",
color: "#fff",
fontSize: 16,
},
data: [],
},
{
name: "border",
type: "pie",
z: 19,
radius: ["0%", "71%"],
center: ["50%", "50%"],
roseType: "radius",
showEmptyCircle: false,
emphasis: { disabled: true },
label: {
show: false,
},
labelLine: {
show: false,
},
data: [],
},
],
渐变色方案:借鉴了其他文章大佬的内容
let data = [
{ value: 40, name: "其他" },
{ value: 28, name: "其他突发事件" },
{ value: 32, name: "森林火险预警" },
{ value: 30, name: "自然灾害" },
{ value: 38, name: "危险源和风险隐患区" },
];
const colorList = [
["rgba(22, 127, 136, 1)", "rgba(12, 235, 253, 1)", "rgba(0, 255, 239, 1)"],
["rgba(148, 129, 42, 1)", "rgba(201, 221, 68, 1)", "rgba(230, 255, 0, 1)"],
["rgba(42, 54, 148, 1)", "rgba(68, 108, 221, 1)", "rgba(155, 177, 255, 1)"],
["rgba(193, 141, 31, 1)", "rgba(236, 165, 13, 1)", "rgba(255, 199, 31, 1)"],
["rgba(24, 55, 141, 1)", "rgba(0, 52, 199, 1)", "rgba(70, 204, 255, 1)"],
];
/**
* 生成data方法
* @data series下的data
* @colorList 颜色数组
*/
const getLinerColorData = (data, colorList) => {
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;
}
});
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: color[1],
},
{
offset: 0.91,
// offset: 1,
color: color[0],
},
],
},
};
data[i].labelLine = {
show: true,
lineStyle: {
color: color[2],
},
};
}
return data;
};
/**
* 线性渐变起止方向的计算方法
* @param {*} startArc 开始角度
* @param {*} endArc 结束角度
* @returns 四个坐标 x,y,x2,y2
*/
const 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);
};
const 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) => {
//@ts-ignore
res[k] = group[idx];
});
return res;
};