0%

前端组件实现-基于vis.js的节点展开

一、数据准备

1.1 概述

  • id最好设置为数值型,方便后面的查找工作,需要通过id值查找其在staticNodes中的下标值
1
2
3
4
5
6
7
8
9
10
11
12
13
// 设置点和边的数据
var staticNodes = [
{id:1, label:"高玥", group:1, pid:0, subids:[2,3]},
{id:2, label:"篮球", group:2, pid:1, subids:[4]},
{id:3, label:"羽毛球", group:2, pid:1},
{id:4, label:"技巧", group:3,pid:2},
]

var staticEdges = [
{id:1,label:"兴趣", from: 1,to: 2},
{id:2,label:"兴趣", from: 1,to: 3},
{id:3,label:"有", from: 2,to: 4},
]

1.2 数据格式

1.2.1 格式要求

  • 一般vis要显示network,需要以下格式
    • node:id、label、group
    • edge:id、label、from、to
  • 为了实现节点展开收缩就需要在nodes中再添加两个字段:
    • pid:父节点id
    • subids:子节点id列表

1.2.2 后端实现方案(首页展示):

  • 需要先给数据分层,添加level标签,表示层级关系,只需要标注第一层即可,因为其他层会因点击事件而进行展开
  • nodes节点中的pid和subids需要进行判断,再进行添加
    • 设置pid:根据关系进行判断(根据to,查找from),对于单向关系,比如(我,有,篮球),那么篮球的pid就设置为“我”的id值,注意:目前只考虑节点的父节点只有一个的情况,如果pid有多个就需要设置列表
    • 设置subids:根据关系进行判断(根据from,查找to),如存在一组关系(我,有,篮球)、(我,年龄,18),那么“我”的subids即表示为“篮球”和“18”对应id组成的列表
    • 设置group:group是为了对节点进行区分
      • 前端实现:在进行节点展开时,会为nodes添加节点数据,这里就可以为节点添加上相同group,可以取一个随机整数
      • 后端实现:对于关系中from标签相同的一组关系,为其to标签对应的节点添加上相同group值
  • 为vis注入数据时,先注入第一层的数据,即先显示第一层的节点关系

二、vis.js绘制network

2.1 将数据注入到vis中

1
2
var nodes = new vis.DataSet(staticNodes);
var edges = new vis.DataSet(staticEdges);

2.2 配置network

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 创建一个图的容器
var container = document.getElementById('mynetwork');

var data = {
nodes: nodes,
edges: edges
};

var options = {
nodes:{
shape: "dot",
// scaling: {
// customScalingFunction: function(min, max, total, value){
// return value / total;
// },
// min:5,
// max:150,
// },
},
edges: {
width: 0.15,
//physics: false,
smooth: {
type: "continuous",
},
},

interaction: {
hover: true,
//hoverConnectedEdges: true
//hideEdgesOnDrag: true,
},

physics: {
//enabled: false,
repulsion:{
nodeDistance: 1,
springLength: 1,
springConstant: 0.5,
centralGravity: 0.9
}
},

layout:{
//hierarchical: {direction: 'UD', sortMethod: 'hubsize'}
}
};

// 图可视化
// initialize your network!
var network = new vis.Network(container, data, options);

2.3 为节点创建点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 点击事件获取的是节点的列表,一般列表中只有一个节点id
network.on('click',function(params){
// 点击的是节点的情况(不考虑点击边的情况)
if (params.nodes.length != 0) {
var clickNodeId = params.nodes[0];
//console.log(findArrayIndexById(staticNodes,clickNodeId));
//removeSubNodes(clickNodeId);
//removeByRecur(clickNodeId)
//console.log(nodes.get(1));

// 逻辑分离,如果点击的节点存在子节点,则递归收缩节点
// 否则进行节点展开
if(getSubNodes(clickNodeId).length!=0)
removeNodes(clickNodeId);
else
addNodes(clickNodeId);

}
});

三、展开收缩逻辑

  • 具体的逻辑实现如下:
    • 第一步:getSubNodes函数获取到点击节点的下级节点id列表subNodes
    • 第二步:判断subNodes是否为空,如果不为空执行第三步,否则执行第四步
    • 第三步:如果不为空:遍历subNodes中的id值,在staticNodes中找到其对应的节点并在nodes/edges中进行递归remove(收缩操作)
    • 第四步:如果为空:判断点击的节点是否存在下级节点列表subids,如果存在,执行第五步(展开操作)
    • 第五步:遍历subids下级id列表,在staticNodes中查找到对应的节点,添加到nodes和edges中,实现节点的展开操作
  • 收缩时,如果点击节点的下一级节点中包含下下一级节点,则进行递归查找并收缩,实现方法为removeNodes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 根据id值查找其对应的下标
function findArrayIndexById(arr, id){
var res;
$.each(arr, (index,item)=>{
if(item['id'] == id){
res = index;
}
});
return res;
}

// 节点收缩
function removeNodes(clickNodeId){
// 获取到的是下级节点列表
var subNodes = getSubNodes(clickNodeId);

//如果获取的下级节点列表长度 不为 0,表示节点没有收缩,则点击后进行收缩
if(subNodes.length != 0){
$.each(subNodes,(index,item)=>{
//消除的是注入到vis中的数据,而不是staticNodes/Edges
removeNodes(item);
nodes.remove({id:item});
edges.remove({from:clickNodeId,to:item});

});
}
}

//节点展开
function addNodes(clickNodeId){
$.each(staticNodes[findArrayIndexById(staticNodes,clickNodeId)]['subids'],(index,item)=>{
nodes.add(staticNodes[findArrayIndexById(staticNodes,item)]);
edges.add({from: clickNodeId,to: item});
});
}

// 获取点击节点的下级节点列表,不包含与其相连的父节点id
function getSubNodes(clickNodeId){
// 获取与该节点所连接的所有节点的id(只要是相连的都会被算进来)
var returnNodes = [];
var connectedNodes = network.getConnectedNodes(clickNodeId);//获取所有连接节点

// 对节点id列表进行遍历,index表示列表下标,item表示对应的id值
$.each(connectedNodes, (index,item)=>{
//这里的clickNodeId和staticNodes数组中的下标只是简单的-1操作
//因为staticNodes中id都是顺序的,如果不是顺序的就需要遍历才能找到其下标值
//clickNodeId是点击的那个节点的id,item是连接节点的id
//如果这个连接的节点是点击的节点父节点,则不考虑进去!!!
if(item != staticNodes[findArrayIndexById(staticNodes,clickNodeId)]['pid']){
returnNodes.push(item);
}
});
return returnNodes;
}

四、实现效果