jtopo 摺疊與展開子節點
通過對象記錄狀態,簡單實現功能:
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;
}
完整的使用例子:
<!DOCTYPE html>
<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 type="text/javascript" src="js/jquery.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>