echarts 自定义的树形拓扑图,各个节点都可自定义
前言
- 为了方便实际业务中有此类需求,先记录下此次的研究成果
- 我这里用的 echarts 版本是 4.9.0 的,但是此案例可以支持 4.9.0 以上的均可,我会把其中关键配置截取出来,伙伴们可以直接复制粘贴到 echarts 官方网站中进行测试效果
- (若哪里写的不对,欢迎友好评论)
效果预览

- 后端接口可以通过如下传递如下结构的数据,控制前端视图渲染的节点和连线样式

完整代码在此!!!
<html>
<header>
<title>zoom tree demo</title>
<style>
html,
body {
margin: 0;
padding: 0;
}
.root {
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 12px;
background-color: rgb(176, 176, 176);
overflow: hidden;
}
#zoom-tree-chart {
background-color: black;
border-radius: 10px;
}
</style>
</header>
<body>
<div class="root">
<div id="zoom-tree-chart" style="height:80vh;width:80vw;"></div>
<button id="reset-btn">重制</button>
</div>
</body>
<script src="./echarts.min.js"></script>
<script>
const mockData = {
name: '标题\n2024-01-01 11:43:55',
color: 'green',
children: [
{
name: '标题12\n2024-01-01 14:18:02',
color: 'rgba(253, 203, 110,1.0)',
lineColor: 'red',
lineType: 'dotted',
children: [
{
name: '标题1\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2'
},
{
name: '标题8\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2\n2024-01-01 14:52:17',
lineColor: 'red',
lineType: 'dotted',
children: [
{
name: '标题2\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2\n2024-01-01 16:06:58'
},
{
name: '标题3\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2\n2024-01-01 12:52:52'
},
{
name: '标题4\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2'
},
{
name: '标题5\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2'
},
{
name: '标题6\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2'
}
],
},
{
name: '标题9'
},
{
name: '标题10'
},
{
name: '标题11\n2024-01-01 14:52:17'
}
],
},
{
name: '标题13',
color: '#E6A23C'
}
]
}
function convertToChartData(data) {
const cardTitleDefaultColor = '#0984e3'
const cardSingleTitleStyle = {
backgroundColor: 'transparent',
color: cardTitleDefaultColor,
align: 'left',
width: '100%',
height: 30,
borderRadius: 0,
fontSize: 16
}
const cardTitleStyle = {
...cardSingleTitleStyle,
}
const cardContentStyle = {
backgroundColor: 'transparent',
color: '#fff',
align: 'left',
width: '100%',
height: 20
}
const nodeData = {
label: {
backgroundColor: '#F4F4F4',
borderRadius: [0, 0, 5, 5],
rich: {},
},
itemStyle: {
color: cardTitleDefaultColor,
borderColor: cardTitleDefaultColor,
borderWidth: 2,
normal: {
color: cardTitleDefaultColor
},
},
lineStyle: {
color: '#888',
width: 1,
type: 'solid'
},
}
const nameString = data.name || ''
const textAry = nameString.split('\n').filter(s => !!s)
if (textAry.length < 1) {
nodeData.label.formatter = ['{first|--}'].join('\n')
nodeData.label.rich.first = cardSingleTitleStyle
} else if (textAry.length === 1) {
nodeData.label.formatter = [`{first|${textAry[0]}}`].join('\n')
nodeData.label.rich.first = cardSingleTitleStyle
} else {
nodeData.label.formatter = [
`{first|${textAry[0]}}`,
`{second|${textAry.slice(1).join('\n')}}`
].join('\n')
nodeData.label.rich.first = cardTitleStyle
nodeData.label.rich.second = cardContentStyle
}
if (data.color) {
nodeData.label.rich.first.color = data.color
nodeData.itemStyle.color = data.color
nodeData.itemStyle.borderColor = data.color
nodeData.itemStyle.normal.color = data.color
}
if (data.lineColor) {
nodeData.lineStyle.color = data.lineColor
}
if (data.lineType) {
nodeData.lineStyle.type = data.lineType
}
if (Array.isArray(data.children) && data.children.length > 0) {
nodeData.children = []
data.children.forEach((child) => {
nodeData.children.push(convertToChartData(child))
})
}
return nodeData
}
function getTreeChartOption(_data) {
const data = convertToChartData(_data)
const option = {
grid: {
left: '2%',
right: '2%',
bottom: '2%',
top: '10%',
containLabel: true
},
tooltip: {
trigger: 'item',
triggerOn: 'mousemove'
},
series: [
{
type: 'tree',
initialTreeDepth: -1,
data: [data],
top: '1%',
left: '7%',
bottom: '1%',
right: '20%',
symbolSize: 10,
zoom: 1,
roam: true,
scaleLimit: {
min: 0.5,
max: 5
},
tooltip: {
show: false
},
label: {
normal: {
position: 'right',
verticalAlign: 'middle',
align: 'left',
color: 'black'
}
},
itemStyle: {
color: data.itemStyle.color,
borderColor: data.itemStyle.borderColor,
},
leaves: {
label: {
normal: {
position: 'right',
verticalAlign: 'middle',
align: 'left'
}
},
itemStyle: {
color: data.itemStyle.color,
borderColor: data.itemStyle.borderColor,
},
},
lineStyle: {
color: data.lineStyle.color,
width: 1,
curveness: 0.5,
type: data.lineStyle.type
}
}
]
}
return option
}
let canvasBoxDom = null
let chart = null
function initChart(apiData) {
this.cleanChart()
canvasBoxDom = document.getElementById('zoom-tree-chart')
chart = echarts.init(canvasBoxDom)
const option = getTreeChartOption(apiData)
if (option && typeof option === 'object') {
chart.setOption(option)
}
}
function cleanChart() {
if (chart) {
chart.dispose()
}
}
function reset(apiData) {
initChart(apiData)
}
function resizeChart() {
if (chart) {
chart.resize()
}
}
initChart(mockData)
let timmer = null
window.addEventListener('resize', ()=>{
if(timmer != null) return
timmer = setTimeout(()=>{
timmer = null
resizeChart()
}, 100)
})
document.getElementById('reset-btn').addEventListener('click',() => {
reset(mockData)
})
</script>
</html>
我用到的 echarts 4.9.0 脚本在此!
可以直接复制到echarts官方网站测试的代码 在此!!!

const mockData = {
name: '标题\n2024-01-01 11:43:55',
color: 'green',
children: [
{
name: '标题12\n2024-01-01 14:18:02',
color: 'rgba(253, 203, 110,1.0)',
lineColor: 'red',
lineType: 'dotted',
children: [
{
name: '标题1\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2'
},
{
name: '标题8\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2\n2024-01-01 14:52:17',
lineColor: 'red',
lineType: 'dotted',
children: [
{
name: '标题2\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2\n2024-01-01 16:06:58'
},
{
name: '标题3\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2\n2024-01-01 12:52:52'
},
{
name: '标题4\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2'
},
{
name: '标题5\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2'
},
{
name: '标题6\n描述1描述1描述1描述1,描述1描述1描述1\n描述2描述2描述2'
}
]
},
{
name: '标题9'
},
{
name: '标题10'
},
{
name: '标题11\n2024-01-01 14:52:17'
}
]
},
{
name: '标题13',
color: '#E6A23C'
}
]
};
function convertToChartData(data) {
const cardTitleDefaultColor = '#0984e3';
const cardSingleTitleStyle = {
backgroundColor: 'rgb(16, 12, 42)',
color: cardTitleDefaultColor,
align: 'left',
width: '100%',
height: 30,
borderRadius: 0,
fontSize: 16
};
const cardTitleStyle = {
...cardSingleTitleStyle
};
const cardContentStyle = {
backgroundColor: 'rgb(16, 12, 42)',
color: 'gray',
align: 'left',
width: '100%',
height: 20
};
const nodeData = {
label: {
backgroundColor: '#F4F4F4',
borderRadius: [0, 0, 5, 5],
rich: {}
},
itemStyle: {
color: cardTitleDefaultColor,
borderColor: cardTitleDefaultColor,
borderWidth: 2,
normal: {
color: cardTitleDefaultColor
}
},
lineStyle: {
color: '#888',
width: 1,
type: 'solid'
}
};
const nameString = data.name || '';
const textAry = nameString.split('\n').filter((s) => !!s);
if (textAry.length < 1) {
nodeData.label.formatter = ['{first|--}'].join('\n');
nodeData.label.rich.first = cardSingleTitleStyle;
} else if (textAry.length === 1) {
nodeData.label.formatter = [`{first|${textAry[0]}}`].join('\n');
nodeData.label.rich.first = cardSingleTitleStyle;
} else {
nodeData.label.formatter = [
`{first|${textAry[0]}}`,
`{second|${textAry.slice(1).join('\n')}}`
].join('\n');
nodeData.label.rich.first = cardTitleStyle;
nodeData.label.rich.second = cardContentStyle;
}
if (data.color) {
nodeData.label.rich.first.color = data.color;
nodeData.itemStyle.color = data.color;
nodeData.itemStyle.borderColor = data.color;
nodeData.itemStyle.normal.color = data.color;
}
if (data.lineColor) {
nodeData.lineStyle.color = data.lineColor;
}
if (data.lineType) {
nodeData.lineStyle.type = data.lineType;
}
if (Array.isArray(data.children) && data.children.length > 0) {
nodeData.children = [];
data.children.forEach((child) => {
nodeData.children.push(convertToChartData(child));
});
}
return nodeData;
}
const data = convertToChartData(mockData);
option = {
tooltip: {
trigger: 'item',
triggerOn: 'mousemove'
},
series: [
{
type: 'tree',
initialTreeDepth: -1,
data: [data],
top: '1%',
left: '7%',
bottom: '1%',
right: '20%',
symbolSize: 10,
zoom: 1,
roam: true,
scaleLimit: {
min: 0.5,
max: 5
},
tooltip: {
show: false
},
label: {
normal: {
position: 'right',
verticalAlign: 'middle',
align: 'left',
color: 'black'
}
},
itemStyle: {
color: data.itemStyle.color,
borderColor: data.itemStyle.borderColor
},
leaves: {
label: {
normal: {
position: 'right',
verticalAlign: 'middle',
align: 'left'
}
},
itemStyle: {
color: data.itemStyle.color,
borderColor: data.itemStyle.borderColor
}
},
lineStyle: {
color: data.lineStyle.color,
width: 1,
curveness: 0.5,
type: data.lineStyle.type
}
}
]
};