如果是只想简单的折叠和展开拓扑图的所有子节点,不对子节点的折叠状态做记录的话很简单。通过去递归遍历所有子节点,然后设置节点的显示属性 visible
就可以了。
如果需要记录子节点的折叠状态,我们可以通过一个对象来记录状态,下面是简单的实现:
var foldOpenStatus = {
}; // 记录折叠状态
function foldOpen(e){
// 折叠展开
var thisNode = e.target.text; // 第一层以当前节点名称为 key 区分折叠状态
var tarlink = e.target.outLinks;
if(tarlink == undefined){
return
}
if(tarlink.length != 0 && tarlink[0].visible === true){
var status = [];
for (var i = 0; i < tarlink.length; i++){
status[i] = {
node: tarlink[i].nodeZ.visible, link: tarlink[i].visible};
foldOpenStatus[thisNode] = status;
tarlink[i].nodeZ.visible = false;
tarlink[i].visible = false;
// 下一层还有节点
if( tarlink[i].nodeZ.outLinks.length != 0){
dbfold(tarlink[i].nodeZ.outLinks, foldOpenStatus[thisNode][i]);
}
}
}else if(tarlink.length != 0 && tarlink[0].visible === false){
for (var k = 0; k < tarlink.length; k++){
tarlink[k].nodeZ.visible = foldOpenStatus[thisNode][k].node;
tarlink[k].visible = foldOpenStatus[thisNode][k].link;
// 下一层还有节点
if( tarlink[k].nodeZ.outLinks.length != 0){
dbOpen(tarlink[k].nodeZ.outLinks, foldOpenStatus[thisNode][k]);
}
}
}
function dbfold(dblink,foldStatus){
var status = []; // 下层以 status 为 key 记录
for(var j = 0; j < dblink.length; j++){
status[j] = {
node: dblink[j].nodeZ.visible, link: dblink[j].visible};
foldStatus.status = status;
dblink[j].nodeZ.visible = false;
dblink[j].visible = false;
if( dblink[j].nodeZ.outLinks.length != 0){
dbfold(dblink[j].nodeZ.outLinks, foldStatus.status[j]);
}
}
}
function dbOpen(dblink, openStatus){
for(var j = 0; j < dblink.length; j++){
dblink[j].nodeZ.visible = openStatus.status[j].node;
dblink[j].visible = openStatus.status[j].link;
if( dblink[j].nodeZ.outLinks.length != 0){
dbOpen(dblink[j].nodeZ.outLinks, openStatus.status[j]);
}
}
}
}
使用姿势:
var thatX,thatY,thisX,thisY;
scene.addEventListener('mousedown', function(e){
if(e.button == 0){
thatX = e.offsetX;
thatY = e.offsetY;
}
});
scene.addEventListener('mouseup', function(event) {
if(event.button == 0){
thisX = event.offsetX;
thisY = event.offsetY;
var distanceX = Math.abs(thisX - thatX);
var distanceY = Math.abs(thisY - thatY);
if(distanceX > 20 || distanceY > 20){
// 区分拖拽和单击事件
}else if(event.target && event.target.elementType == "node"){
event.target.removeEventListener("click");
event.target.addEventListener("click",function(event){
foldOpen(event); // 折叠与展开子节点功能
});
}
}
});
需要注意,在节点创建的时候需要添加 click
事件监听,哪怕什么也不做:
function addNode(text){
var node = new JTopo.Node(text);
node.addEventListener("click",function(event){
// 此事件不可删除
});
scene.add(node);
return node;
}
下面是一个完整的使用例子:
<html>
<head>
<meta charset="UTF-8">
<title> jtopo 子节点折叠展开 + 子节点环形布局 DEMO title>
<style>
body{
padding: 0;
margin: 0;
}
.controlLeftBtn{
width: 50px;
height: 31px;
cursor: pointer;
position: absolute;
}
style>
head>
<body>
<div>
<canvas id="canvas" width="1707" height="826">canvas>
div>
<script type="text/javascript" src="js/jtopo-min.js">script>
<script>
// 高度响应式
let canvasDom = document.querySelector("#canvas");
canvasDom.setAttribute('height',document.documentElement.clientHeight - 5);
canvasDom.setAttribute('width',document.documentElement.clientWidth - 3);
// 拓扑图数据
var myData = [
{
"hostName":"SWITCH_192_168_123_43","hostType":"2","hostHModel":"MS3228","hostStatus":"1","list":[
{
"hostName":"SWITCH_192_168_123_63","hostType":"2","hostHModel":"MS3228","hostStatus":"1","list":[
{
"hostName":"SWITCH_192_168_123_64","hostType":"2","hostHModel":"MS3228","hostStatus":"1","list":[
{
"hostName":"AP192-168-123-62","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-61","hostType":"1","hostStatus":"1","hostHModel":"MP223"}
]}
]},
{
"hostName":"SWITCH_192_168_123_63","hostType":"2","hostHModel":"MS3228","hostStatus":"1","list":[
{
"hostName":"SWITCH_192_168_123_64","hostType":"2","hostHModel":"MS3228","hostStatus":"1","list":[
{
"hostName":"AP192-168-123-62","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-61","hostType":"1","hostStatus":"1","hostHModel":"MP223"}
]},
{
"hostName":"SWITCH_192_168_123_64","hostType":"2","hostHModel":"MS3228","hostStatus":"1","list":[
{
"hostName":"AP192-168-123-62","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-61","hostType":"1","hostStatus":"1","hostHModel":"MP223","list":[
{
"hostName":"AP192-168-123-62","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-61","hostType":"1","hostStatus":"1","hostHModel":"MP223"}
]}
]}
]},
{
"hostName":"SWITCH_192_168_123_63","hostType":"2","hostHModel":"MS3228","hostStatus":"1","list":[
{
"hostName":"SWITCH_192_168_123_64","hostType":"2","hostHModel":"MS3228","hostStatus":"1","list":[
{
"hostName":"AP192-168-123-62","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-61","hostType":"1","hostStatus":"1","hostHModel":"MP223","list":[
{
"hostName":"AP192-168-123-62","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-61","hostType":"1","hostStatus":"1","hostHModel":"MP223","list":[
{
"hostName":"AP192-168-123-62","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-61","hostType":"1","hostStatus":"1","hostHModel":"MP223","list":[
{
"hostName":"AP192-168-123-62","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-47","hostType":"1","hostStatus":"1","hostHModel":"MP223"},
{
"hostName":"AP192-168-123-61","hostType":"1","hostStatus":"1","hostHModel":"MP223"}
]}
]}
]}
]}
]}
]},
{
"hostName":"AP192-168-123-32","hostType":"1","hostHModel":"MP223","hostStatus":"1"},
{
"hostName":"AP192-168-123-32","hostType":"1","hostHModel":"MP223","hostStatus":"1"},
{
"hostName":"AP192-168-123-32","hostType":"1","hostHModel":"MP223","hostStatus":"1"},
{
"hostName":"AP192-168-123-32","hostType":"1","hostHModel":"MP223","hostStatus":"1"},
{
"hostName":"AP192-168-123-32","hostType":"1","hostHModel":"MP223","hostStatus":"1"},
{
"hostName":"AP192-168-123-32","hostType":"1","hostHModel":"MP223","hostStatus":"1"},
{
"hostName":"AP192-168-123-51","hostType":"1","hostHModel":"MP223","hostStatus":"1"},
{
"hostName":"AP192-168-123-37","hostType":"1","hostHModel":"MP223","hostStatus":"1"}
];
// 画图
drawTopo(myData);
function drawTopo(data){
var _strokeColor = '129,169,129'; // 线条颜色
var _strokeColorFalut = '210,129,129'; // 离线线条颜色
// 分离不同类型的节点
var swiArrHasList = []; // 存在下层节点的交换机
var justArr = []; // 不存在下层节点的交换机或者 AP
for(var i=0;i<data.length;i++){
if(data[i].hostType == '2' && data[i].list ){
// 交换机,有下层节点
swiArrHasList.push(data[i]);
}
if((data[i].hostType == '2' && !data[i].list) || (data[i].hostType == '1') ){
// 交换机,没有下层节点 或者 ap
justArr.push(data[i])
}
}
// 开始画图
var canvas = document.getElementById('canvas');
var stage = new JTopo.Stage(canvas);
var scene = new JTopo.Scene();
var thatX,thatY,thisX,thisY;
scene.addEventListener('mousedown', function(e){
if(e.button == 0){
thatX = e.offsetX;
thatY = e.offsetY;
}
});
scene.addEventListener('mouseup', function(event) {
if(event.button == 0){
thisX = event.offsetX;
thisY = event.offsetY;
var distanceX = Math.abs(thisX - thatX);
var distanceY = Math.abs(thisY - thatY);
if(distanceX > 20 || distanceY > 20){
// 区分拖拽和单击事件
if(event.target && event.target.elementType == "node"){
event.target.removeEventListener("click");
if (event.target && event.target.layout) {
JTopo.layout.layoutNode(scene, event.target, true);
}
}
}else if(event.target && event.target.elementType == "node"){
event.target.removeEventListener("click");
event.target.addEventListener("click",function(event){
foldOpen(event);
});
}
}
});
// 流式布局(水平、垂直间隔)
var flowLayout = JTopo.layout.FlowLayout(100, 40);
stage.wheelZoom = 1.25; // 滚轮缩放
scene.alpha = 1; // 通道透明度
scene.backgroundColor = '54,57,63';
stage.add(scene);
function addNode(text){
var node = new JTopo.Node(text);
scene.add(node);
node.addEventListener("click",function(event){
// 此事件不可删除
});
// 修改节点选中样式
node.paintSelected = function (a){
0 != this.showSelected && (a.save(), a.beginPath(), a.strokeStyle = "rgba(218,221,221, 0.7)", a.fillStyle = "rgba(218,221,221,0.7)", a.arc( 0, 0, this.width/2 + 3, 0, Math.PI*2, false), a.fill(), a.stroke(), a.closePath(), a.restore())
};
return node;
}
function addLink(nodeA, nodeZ, linkColor){
var link = new JTopo.Link(nodeA, nodeZ);
link.strokeColor = linkColor ||'204,204,204';
link.lineWidth = 1;
scene.add(link);
return link;
}
// 不存在下层节点的设备,通过容器布局
if(justArr.length != 0 ){
var containerJustArr = new JTopo.Container('Just Container');
containerJustArr.textPosition = 'Top_Center';
containerJustArr.dragable = false;
containerJustArr.fontColor = '180,180,180';
containerJustArr.font = '14pt 微软雅黑';
containerJustArr.alpha = 0; // 容器显示
containerJustArr.fillColor = '242,242,242';
containerJustArr.borderColor = '50,50,50';
containerJustArr.borderRadius = 10; // 圆角
containerJustArr.layout = flowLayout; // 不指定布局的时候,容器的布局为自动(容器边界随元素变化)
scene.add(containerJustArr);
var _justLen = justArr.length;
var justcontainerW = Math.round(Math.sqrt(_justLen))*(110+20)/2; // 根据节点格式计算盒子宽度
var justcontainerH = Math.round(Math.sqrt(_justLen))*(100+20);
// 如果只有不存在下层节点的设备
if(swiArrHasList.length == 0 ){
justcontainerW = Math.round(Math.sqrt(_justLen))*(110+20)*1.5;
justcontainerH = Math.round(Math.sqrt(_justLen))*(100+20)/1.5;
}
containerJustArr.setBound(100, 100, justcontainerW, justcontainerH);
for(var _ja = 0; _ja<justArr.length; _ja++){
var justNode = addNode(justArr[_ja].hostName);
scene.add(justNode);
containerJustArr.add(justNode)
}
}
// 存在下层节点的设备使用环形布局
if(swiArrHasList.length != 0){
var _hasListLen = swiArrHasList.length;
var centerRadius = 500;
var thisHostLayer = getHostLayerNum(_hasListLen); // 获取每个中心交换机的层数
function getHostLayerNum(_hasListLen){
var next = 0;
var hostArr = [];
for(var _w=0; _w<_hasListLen; _w++){
hostArr[_w] = 0;
getLayerNum(swiArrHasList[_w],1,_w)
}
// 获取每个中心交换机的层数
function getLayerNum(node,num,_w){
// 节点,层数,第几个中心节点
if(node.list){
num++;
for(var _l=0; _l<node.list.length; _l++){
getLayerNum(node.list[_l],num,_w);
}
}else{
next = num;
if(num > hostArr[_w]){
hostArr[_w] = num;
}
next = 0;
}
}
return hostArr;
}
var positionArr = [];
var justLen = justArr.length || 1;
var listNodeTop = 50 * justLen;
for(var i = 0; i < _hasListLen; i++){
// 画中心交换机
var listNode = addNode(swiArrHasList[i].hostName);
var nextLevelLen = swiArrHasList[i].list ? swiArrHasList[i].list.length : 0;
var centerRadius = nextLevelLen ? (nextLevelLen * 300)/(2 * Math.PI) : 130;
var listRadius = 0;
if(swiArrHasList[i].list ){
// 画下层设备
console.log("================ level "+ (thisHostLayer[i]-1) +"=================");
listRadius = dragListDevice(thisHostLayer[i]-1, swiArrHasList[i].list, listNode);
}
// 中心交换机的位置
listNode.setLocation(listRadius * (thisHostLayer[i]-1), listNodeTop);
listNode.layout = {
type: 'circle', radius: centerRadius + listRadius};
scene.add(listNode);
JTopo.layout.layoutNode(scene, listNode, true); // 圆形布局
}
// 画下层设备
function dragListDevice(layerNum, list, listNode){
var circleRadius = 130;
for(var i = 0; i < list.length; i++){
var nextLevelLen = list[i].list ? list[i].list.length : 0;
circleRadius = nextLevelLen ? (nextLevelLen * 150)/(2 * Math.PI) : 130;
var dlNode = addNode(list[i].hostName);
// 如果存在下层设备
if( list[i].list && layerNum-1){
circleRadius += dragListDevice(layerNum-1, list[i].list, dlNode);
}
dlNode.layout = {
type: 'circle', radius: circleRadius };
var dlLink = addLink(listNode, dlNode);
dlLink.strokeColor = list[i].hostStatus == 1 ? _strokeColorFalut : _strokeColor;
scene.add(dlNode);
scene.add(dlLink);
}
return circleRadius
}
}
stage.centerAndZoom(); //缩放并居中显示
// stage.zoomIn();
}
function rd(n,m){
// JS获取n至m随机整数
var c = m-n+1;
return Math.floor(Math.random() * c + n);
}
var foldOpenStatus = {
}; // 记录折叠状态
function foldOpen(e){
// 折叠展开
var thisNode = e.target.text; // 以当前节点名称为 key
var tarlink = e.target.outLinks;
if(tarlink == undefined){
return
}
if(tarlink.length != 0 && tarlink[0].visible === true){
var status = [];
for (var i = 0; i < tarlink.length; i++){
status[i] = {
node: tarlink[i].nodeZ.visible, link: tarlink[i].visible};
foldOpenStatus[thisNode] = status;
tarlink[i].nodeZ.visible = false;
tarlink[i].visible = false;
// 下一层还有节点
if( tarlink[i].nodeZ.outLinks.length != 0){
dbfold(tarlink[i].nodeZ.outLinks, foldOpenStatus[thisNode][i]);
}
}
}else if(tarlink.length != 0 && tarlink[0].visible === false){
for (var k = 0; k < tarlink.length; k++){
tarlink[k].nodeZ.visible = foldOpenStatus[thisNode][k].node;
tarlink[k].visible = foldOpenStatus[thisNode][k].link;
// 下一层还有节点
if( tarlink[k].nodeZ.outLinks.length != 0){
dbOpen(tarlink[k].nodeZ.outLinks, foldOpenStatus[thisNode][k]);
}
}
}
function dbfold(dblink,foldStatus){
var status = [];
for(var j = 0; j < dblink.length; j++){
status[j] = {
node: dblink[j].nodeZ.visible, link: dblink[j].visible};
foldStatus.status = status;
dblink[j].nodeZ.visible = false;
dblink[j].visible = false;
if( dblink[j].nodeZ.outLinks.length != 0){
dbfold(dblink[j].nodeZ.outLinks, foldStatus.status[j]);
}
}
}
function dbOpen(dblink, openStatus){
for(var j = 0; j < dblink.length; j++){
dblink[j].nodeZ.visible = openStatus.status[j].node;
dblink[j].visible = openStatus.status[j].link;
if( dblink[j].nodeZ.outLinks.length != 0){
dbOpen(dblink[j].nodeZ.outLinks, openStatus.status[j]);
}
}
}
}
script>
body>
html>