最近出了个需求,要求做一个数据流向的图表。
根据数据结构特点:数据可以通过多个节点开始流入,最终可以流向多个节点。根据这个特点,满足要求的有两个:一个是关系图,另一个是桑基图。再根据各节点的排列位置来看,桑基图是最接近的了
但是设计师觉得桑基图的样式不好看,所以要基于桑基图来调整。
一般的桑基图是这样的
在阅读echarts的桑基图的文档之后,节点的高度是自动的,宽度可以设置,但是只能统一设置,不能对节点单独设置。这样的比较麻烦了,不太好自定义了。
但是,echarts还有个label属性,我们可以通过echarts的label的富文本属性来自定义节点的样式【太妙了】
下面直接上代码:
(这里的代码的主要终点就是对node的label的设置,已经link的lineStyle的设置,其他的根绝自己的需求调整即可)
使用的技术栈:vue3.0 + ts
function handleInitEchart(data: any[], links: any[]) {
const nodesMap: any = {}, linksCountMap: any = {};
const colors: any = [
"#1C4BA0",
"#F99F46",
"#F4654E",
"#5A2EF7",
"#0086FF",
"#FF6600",
"#FF6666",
];
const bigColor: any = [
"#336699",
"#FF6600",
"#990033",
"#FF6666",
"#009966",
"#CC6600",
"#003366",
"#CC3333",
"#006699",
"#FFCCCC",
];
for (let i = 0; i < links.length; i++) {
let item = links[i];
if (!linksCountMap[item.target]) {
linksCountMap[item.target] = [item.source];
} else {
linksCountMap[item.target].push(item.source);
}
}
links.forEach((item: any) => {
if (linksCountMap[item.target].includes(item.source)) {
item.value = parseInt(
(60 / linksCountMap[item.target].length) as any
);
} else {
item.value = 10;
}
if (item.value > 10) {
item.value = 10;
}
Object.assign(item, {
lineStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{
offset: 0,
color: colors[item.level - 1],
},
{
offset: 1,
color: colors[item.level],
},
]),
},
});
});
for (let i = 0; i < data.length; i++) {
data[i].count = data[i].value;
}
data.forEach((item: any, idx: number) => {
if (!nodesMap[item.level]) {
nodesMap[item.level] = [item];
} else {
nodesMap[item.level].push(item);
}
item.value = 30;
item.name = "节点名称" + idx;
let offsetY = 8;
let padding = [4, 10],
color = colors[item.level - 1];
if (item.level == "1") {
if (item.count > 5000000) {
item.value = 28;
padding = [10, 10];
offsetY = 8;
let colori = parseInt((Math.random() * 7) as any);
color = bigColor[colori];
} else {
item.value = 16;
padding = [4, 10];
offsetY = 4;
}
} else {
item.value = 60;
padding = [17, 10];
offsetY = 20;
if (linksCountMap[item.id].length > 3) {
offsetY = 0;
}
}
Object.assign(item, {
level: item.level,
nodeWidth: 270,
label: {
offset: [0, -offsetY],
width: 247,
show: true,
position: "inside",
padding: padding,
borderColor: color,
borderWidth: 1,
borderRadius: 4,
borderType: "solid",
backgroundColor: color,
fontSize: item.level == "1" ? 12 : 20,
formatter(e: any) {
if (item.level == "1") {
return `{a|${e.data.name}}{b|${e.data.count}}`;
} else {
return `{c|${e.data.name}}\n{d|${e.data.count}}`;
}
},
},
});
});
const option = {
series: [
{
type: "sankey",
left: 20,
top: 20,
right: 20,
bottom: 20,
data: data,
links: links,
nodeWidth: 270,
draggable: false,
focusNodeAdjacency: "allEdges",
lineStyle: {
color: "source",
curveness: 0.5,
},
itemStyle: {
color: "transparent",
borderColor: "transparent",
},
label1: {
position: "inside",
fontSize: 12,
color: "#666",
formatter: function (params: any) {
let { data } = params;
return data.name;
},
},
label: {
position: "inside",
show: true,
color: "#fff",
fontSize: 12,
rich: {
a: {
width: 200,
align: "left",
color: "#fff",
},
b: {
color: "#fff",
align: "right",
},
c: {
width: 200,
fontSize: 24,
align: "center",
},
d: {
fontSize: 24,
align: "center",
},
},
},
},
],
tooltip: {
trigger: "item",
},
};
echartsOptions.value = option;
}
最终的效果图: