echarts下载: echarts下载
其次先看一下我改的效果图:
接下来分段解释一下需要的代码:
1.声明一个全局的变量:
var globalData = []; //用来存放被收起的某节点的子节点
2. 在data节点数据中添加category字段,一开始就上代码好多人可能会很闷,具体如下图所示稍作解释:
3.点击事件
/**
* 单击折叠功能
* 点击隐藏节点实现,定义数组globalData用来存储被隐藏的节点的子节点
*/
myChart.on('click', function(params) {
if (params.dataType === "node") {
var deletedFlag = false; // 标记点击的此节点是否存在子节点,若不存在则说明可能在上次的操作中已经删除,这时就需要尝试把之前删除的节点重新添加进去
for (var i = data.length - 1; i >= 0; i--) {
if (data[i].category == params.data.id) {
if (data[i].category != data[i].id) { //排除删除根元素的可能
deletedFlag = true;
for (var ii = data.length - 1; ii >= 0; ii--) { //删除第一级节点的子节点
if (data[ii].category == data[i].id) {
for (var iii = data.length - 1; iii >= 0; iii--) { //删除第二级节点的子节点
if (data[iii].category == data[ii].id) {
globalData.push(data[iii]);
data.splice(iii, 1);
}
}
globalData.push(data[ii]);
data.splice(ii, 1);
}
}
globalData.push(data[i]);
data.splice(i, 1); //删除该元素的第一级子节点,最多需删除三级
}
}
}
if (!deletedFlag) { //这种情况下需要恢复该节点的子节点
var nodeChildren = []; //存放本次恢复的数据,然后将它们从globalData中删除
for (var n = globalData.length - 1; n >= 0; n--) {
if (params.data.id == globalData[n].category) { //显示该节点第一级子节点
data.push(globalData[n]);
nodeChildren.push(globalData[n]);
for (var nn = globalData.length - 1; nn >= 0; nn--) {
if (globalData[n].id == globalData[nn].category) { //显示该节点第二级子节点
data.push(globalData[nn]);
nodeChildren.push(globalData[nn]);
for (var nnn = globalData.length - 1; nnn >= 0; nnn--) {
if (globalData[nn].id == globalData[nnn].category) { //显示该节点第三级子节点
data.push(globalData[nnn]);
nodeChildren.push(globalData[nnn]);
}
}
}
}
}
}
if (nodeChildren.length > 0) {
for (var s = 0; s < nodeChildren.length; s++) {
for (var n = globalData.length - 1; n >= 0; n--) {
if (nodeChildren[s].id == globalData[n].id) {
globalData.splice(n, 1);
}
}
}
}
}
myChart.setOption(option);
}
});
最后完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>graph折叠</title>
<!-- 引入 echarts.js -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script type="text/javascript" src="js/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="js/echars.js"></script>
<style>
*{padding: 0;margin: 0;}
.menu{
/*这个样式不写,右键弹框会一直显示在画布的左下角*/
position: absolute;
background: rgba(3,3,3,0.6);
border-radius: 5px;
left: -99999px;
top: -999999px;
}
.menu ul{list-style: none}
.menu ul li{
padding: 5px 10px;
color: #ffff;
border-bottom: 1px dashed #ffffff;
font-size: 14px;
cursor: pointer;
}
.menu ul li:hover{
color: #659bc5;
}
.menu ul li:last-child{
border-bottom: none;
}
</style>
</head>
<body>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div class="tree-container">
<div id="main" style="width: 600px;height:500px;"></div>
</div>
<!--右键弹出菜单-->
<div id="rightMenu" class="menu" style="display: none;">
<ul>
<li><span class="glyphicon glyphicon-off" aria-hidden="true"></span> 下线</li>
<li><span class="glyphicon glyphicon-road" aria-hidden="true"></span> 通过</li>
</ul>
</div>
<script>
var myChart = echarts.init(document.getElementById('main'));
var globalData = []; //用来存放被收起的某节点的子节点
//todo 节点信息
var data = [
{
"id": 0,
"category": "0",
"name": "外部网络",
"type": "Internet",
"ip":"1.1.1.1",
"port":"未知",
"ignore":"false",
"flag":"true"
}, {
"id": 1,
"category": "0",
"name": "交换机",
"type": "switch",
"ip":"192.168.30.125",
"mac":"48:de:3d:e2:49:a8",
"model":"H3C",
"uptime":"2020-09-03 10:50:50",
"port":"22",
"ignore":"true",
"flag":"true"
}, {
"id": 2,
"category": "1",
"name": "交换机",
"type": "switch",
"ip":"192.168.1.8",
"mac":"cd:bd:3d:e2:55:55",
"model":"pf",
"uptime":"2020-09-03 10:50:50",
"port":"33",
"ignore":"true",
"flag":"true"
}, {
"id": 3,
"category": "1",
"name": "计算机",
"type": "computer",
"ip":"192.168.1.8",
"mac":"dv:bd:fd:e2:df:fd",
"model":"pf",
"uptime":"2020-09-03 10:50:50",
"account":"xiaox",
"location":"xianm",
"port":"44",
"ignore":"true",
"flag":"true"
}, {
"id": 4,
"category": "1",
"name": "路由器",
"type": "rooter",
"ip":"192.168.1.8",
"mac":"ds:bd:3d:e2:ds:55",
"model":"pf",
"uptime":"2020-09-03 10:50:50",
"account":"xiaox",
"location":"xianm",
"port":"55",
"ignore":"true",
"flag":"true"
}, {
"id": 5,
"category": "1",
"name": "服务器",
"type": "service",
"ip":"192.168.1.8",
"mac":"vf:eq:dd:e2:55:55",
"model":"pf",
"uptime":"2020-09-03 10:50:50",
"account":"xiaox",
"location":"xianm",
"port":"66",
"ignore":"true",
"flag":"true"
}, {
"id":6,
"category": "2",
"name": "打印机",
"type": "print",
"ip":"192.168.1.8",
"mac":"ss:bd:3d:ju:55:55",
"model":"pf",
"uptime":"2020-09-03 10:50:50",
"account":"xiaox",
"location":"xianm",
"port":"77",
"ignore":"true",
"flag":"true"
}, {
"id": 7,
"category": "2",
"name": "手机",
"type": "phone",
"ip":"192.168.1.8",
"mac":"ju:ju:3d:e2:55:uy",
"model":"pf",
"uptime":"2020-09-03 10:50:50",
"account":"xiaox",
"location":"xianm",
"port":"88",
"ignore":"true",
"flag":"true"
}
];
//todo 连线信息(线条中的source和target都对应节点data数据中的id指向)
//todo 我看了其他例子,好像也可以是其它的字段,不一定是id
var links = [{
"source": 0,
"target": 1
}, {
"source": 1,
"target": 2
}, {
"source": 1,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 1,
"target": 5
}, {
"source": 2,
"target": 6
}, {
"source": 2,
"target": 7
}];
/**
* 自定义图片
*/
for (var i = 0;i < data.length;i++){
if (data[i].type == 'Internet'){
data[i].symbol = 'image://image/internet.png';
data[i].symbolSize = 70;
}
if (data[i].type == 'switch'){
data[i].symbol = 'image://image/switch.png';
}
if (data[i].type == 'hub'){
data[i].symbol = 'image://image/hub.png';
}
if (data[i].type == 'computer'){
data[i].symbol = 'image://image/computer.png';
}
if (data[i].type == 'rooter'){
data[i].symbol = 'image://image/rooter.png';
}
if (data[i].type == 'service'){
data[i].symbol = 'image://image/service.png';
}
if (data[i].type == 'print'){
data[i].symbol = 'image://image/print.png';
}
if (data[i].type == 'phone'){
data[i].symbol = 'image://image/phone.png';
}
}
// var categoryName = [];
// var categoryStatus = {'外部网络':true,'交换机':true,'hub':true};
// var categories = [ {name : '外部网络'}, {name : '交换机'}, {name : 'hub'}];
var option = {
tooltip: {//弹窗
enterable:true,//鼠标是否可进入提示框浮层中
formatter:formatterHover,//修改鼠标悬停显示的内容
},
toolbox: {
show: true,
top:20,
left:20,
feature: {
restore: {
title:'刷新'//刷新echarts图标
},
saveAsImage: {
title:'下载图片',//鼠标悬停在下载图标上时,显示的文字
name:'network-topology'//下载图片的文件名为network-topology.png
}
}
},
color:['#09022C',
'#040193',
'#073CFE',
'#0065C2'],
series : [ {//图片配置
type : 'graph', //关系图
//name : "拓扑图", //系列名称,用于tooltip的显示,legend 的图例筛选,在 setOption 更新数据和配置项时用于指定对应的系列。
layout : 'force', //图的布局,类型为力导图,'circular' 采用环形布局,见示例 Les Miserables
legendHoverLink : true,//是否启用图例 hover(悬停) 时的联动高亮。
hoverAnimation : true,//是否开启鼠标悬停节点的显示动画
coordinateSystem : null,//坐标系可选
xAxisIndex : 0, //x轴坐标 有多种坐标系轴坐标选项
yAxisIndex : 0, //y轴坐标
force: {
repulsion: 450,//相距距离
edgeLength: [150, 200],
layoutAnimation: true
},
roam : true,//是否开启鼠标缩放和平移漫游。默认不开启。如果只想要开启缩放或者平移,可以设置成 'scale' 或者 'move'。设置成 true 为都开启
nodeScaleRatio : 0.6,//鼠标漫游缩放时节点的相应缩放比例,当设为0时节点不随着鼠标的缩放而缩放
draggable : true,//节点是否可拖拽,只在使用力引导布局的时候有用。
focusNodeAdjacency : true,//是否在鼠标移到节点上的时候突出显示节点以及节点的边和邻接节点。
edgeSymbol : [ 'none', 'arrow' ],//边两端的标记类型,可以是一个数组分别指定两端,也可以是单个统一指定。默认不显示标记,常见的可以设置为箭头,如下:edgeSymbol: ['circle', 'arrow']
symbolSize:[40,40],//图形大小
edgeSymbolSize : 10,//边两端的标记大小,可以是一个数组分别指定两端,也可以是单个统一指定。
lineStyle : { //todo==========节点连线样式。
normal : {
color : '#31354B',
width : '1',
type : 'solid', //线的类型 'solid'(实线)'dashed'(虚线)'dotted'(点线)
curveness : 0, //线条的曲线程度,从0到1
opacity : 1
// 图形透明度。支持从 0 到 1 的数字,为 0 时不绘制该图形。默认0.5
},
emphasis : {//高亮状态
}
},
label : { //todo=============图形上的文本标签(图片名称)
normal : {
show : true,//是否显示标签。
position : 'bottom',//标签的位置。['50%', '50%'] [x,y] 'inside'
textStyle : { //标签的字体样式
color : '#2D2F3B', //字体颜色
fontStyle : 'normal',//文字字体的风格 'normal'标准 'italic'斜体 'oblique' 倾斜
fontWeight : 'bolder',//'normal'标准'bold'粗的'bolder'更粗的'lighter'更细的或100 | 200 | 300 | 400...
fontFamily : 'sans-serif', //文字的字体系列
fontSize : 12, //字体大小
}
},
emphasis : {//高亮状态
}
},
data: data,
links: links //edges是其别名代表节点间的关系数据。
} ]
};
/**
* 鼠标悬停时显示详情
*/
function formatterHover(params){
// console.log(params);
// console.log('data1',data);
//todo 节点node悬停效果
if (params.dataType == "node") {
var deviceType = params.data.type;
var imgPath = params.data.symbol;
//图片地址截取,因为echarts修改图片的时候有一个------image://---前缀,前缀后面的才是图片真正的地址
var imgPathSrc = imgPath.split("image://")[1];
// console.log('str',imgPathSrc);
if (deviceType === 'Internet' || deviceType === 'hub'){
return "<img src='"+imgPathSrc+" ' width='30px' height='30px'>" + '<span style="padding:0 5px;font-size: 14px;">'+ params.data.name+'</span>';
// return firstParams.name + ' ' + firstParams.seriesName + '<br>' + '装机:' + firstParams.data + ' 亿千瓦<br>增长率:' + sndParams.data +' %';
} if (deviceType === 'switch'){
return "<img src='"+imgPathSrc+" ' width='30px' height='30px'>" + '<span style="padding: 0 5px;font-size: 14px;">设备类型:'+ params.data.name+'</span>'+ '<br>'
+ '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">IP:'+ params.data.ip+'</span>'+ '<br>'
+ '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">MAC:'+ params.data.mac+'</span>'+ '<br>'
+ '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">设备型号:'+ params.data.model+'</span>'+ '<br>'
+'<button style="padding:2px 5px;border:none;outline:none;color:#ffffff;border-radius: 5px;background:rgba(0,0,0,0.5);"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> 路由列表</button>'
+'<button style="padding:2px 5px;border:none;outline:none;color:#ffffff;border-radius: 5px;background:rgba(0,0,0,0.5);margin-left: 10px;"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> ARP列表</button>';
}else{
return "<img src='"+imgPathSrc+" ' width='30px' height='30px'>" + '<span style="padding: 0 5px;font-size: 14px;">设备类型:'+ params.data.name+'</span>'+ '<br>'
+ '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">IP:'+ params.data.ip+'</span>'+ '<br>'
+ '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">MAC:'+ params.data.mac+'</span>'+ '<br>'
+ '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">账号:'+ params.data.account+'</span>'+ '<br>'
+ '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">所在位置:'+ params.data.location+'</span>'+ '<br>'
+ '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">最后登录时间:'+ params.data.uptime+'</span>';
}
}
//todo 自定义线条悬停效果,因为线条中没有node节点的信息,所有自定义拿到节点信息,显示悬停效果
if (params.dataType == "edge") {
var linkName = params.name;
var nameString = linkName.split(">");//将字符串以“>”隔开
console.log('nameString',nameString);
var linkNameFirst = nameString[0].trim();//截取“>”之前的数字--------.trim()因为取的数字中有空格,去掉空格
var linkNameLast = nameString[1].trim();//截取“>”之后的数字
//todo 以前的方法写的太局限
//todo 例如:411>2223,截取第一位是4,最后一位是3,不是我们要的完整的数据
// var linkNameFirst = linkName.substring(0,1);//截取第一位
// var linkNameLast = linkName.substring(linkName.length-1,linkName.length);//截取最后一位
console.log('linkName',linkName);
console.log('params',params);
console.log('linkNameFirst',linkNameFirst);
console.log('linkNameLast',linkNameLast);
var dataList = [];//存放线条两边的节点node数据
for (var j =0;j<data.length;j++){
if (linkNameFirst == data[j].id || linkNameLast == data[j].id){
// console.log('j',data[j]);
dataList.push(data[j]);
}
}
// console.log('dataList',dataList);
return '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">名称:'+ dataList[0].name+'->'+dataList[1].name+'</span>'+ '<br>'
+ '<span style="padding-left:5px;height:30px;line-height:30px;display: inline-block;font-size: 14px;">端口号:'+ dataList[0].ip+':'+dataList[0].port+'->'+dataList[1].ip+':'+dataList[1].port+'</span>';
}
}
/**
* 鼠标右键,弹出右键操作菜单
*/
$("#main").bind("contextmenu", function () { return false; });//防止默认菜单弹出(查看图像,图像另存为等)
myChart.on("contextmenu", function(params){
// console.log('params',params.data.type);
var rightType = params.data.type;
if(rightType != "Internet" && rightType != "switch" && rightType != "hub"){
$('#rightMenu').css({
'display': 'block',
'left': params.event.offsetX + 15,
'top' : params.event.offsetY + 15
});
//todo 右键菜单刚弹出时,隐藏鼠标悬停菜单
$("#main>div:nth-child(2)").hide();
}
});
/**
* 点击画布的时候隐藏右键菜单
*/
$('.tree-container').click(function () {
$('#rightMenu').css({
'display': 'none',
'left': '-9999px',
'top': '-9999px'
});
});
myChart.setOption(option);
/**
* 单击折叠功能
* 点击隐藏节点实现,定义数组globalData用来存储被隐藏的节点的子节点
*/
myChart.on('click', function(params) {
if (params.dataType === "node") {
var deletedFlag = false; // 标记点击的此节点是否存在子节点,若不存在则说明可能在上次的操作中已经删除,这时就需要尝试把之前删除的节点重新添加进去
for (var i = data.length - 1; i >= 0; i--) {
if (data[i].category == params.data.id) {
if (data[i].category != data[i].id) { //排除删除根元素的可能
deletedFlag = true;
for (var ii = data.length - 1; ii >= 0; ii--) { //删除第一级节点的子节点
if (data[ii].category == data[i].id) {
for (var iii = data.length - 1; iii >= 0; iii--) { //删除第二级节点的子节点
if (data[iii].category == data[ii].id) {
globalData.push(data[iii]);
data.splice(iii, 1);
}
}
globalData.push(data[ii]);
data.splice(ii, 1);
}
}
globalData.push(data[i]);
data.splice(i, 1); //删除该元素的第一级子节点,最多需删除三级
}
}
}
if (!deletedFlag) { //这种情况下需要恢复该节点的子节点
var nodeChildren = []; //存放本次恢复的数据,然后将它们从globalData中删除
for (var n = globalData.length - 1; n >= 0; n--) {
if (params.data.id == globalData[n].category) { //显示该节点第一级子节点
data.push(globalData[n]);
nodeChildren.push(globalData[n]);
for (var nn = globalData.length - 1; nn >= 0; nn--) {
if (globalData[n].id == globalData[nn].category) { //显示该节点第二级子节点
data.push(globalData[nn]);
nodeChildren.push(globalData[nn]);
for (var nnn = globalData.length - 1; nnn >= 0; nnn--) {
if (globalData[nn].id == globalData[nnn].category) { //显示该节点第三级子节点
data.push(globalData[nnn]);
nodeChildren.push(globalData[nnn]);
}
}
}
}
}
}
if (nodeChildren.length > 0) {
for (var s = 0; s < nodeChildren.length; s++) {
for (var n = globalData.length - 1; n >= 0; n--) {
if (nodeChildren[s].id == globalData[n].id) {
globalData.splice(n, 1);
}
}
}
}
}
myChart.setOption(option);
}
});
</script>
</body>
</html>