<!DOCTYPE
html>
<html>
<head>
<script
type="text/javascript"
src="http://mbostock.github.com/d3/d3.v2.js?2.9.1"></script>
<style
type="text/css">
.link
{ stroke: green; stroke-linejoin:bevel;}
.link_error{
stroke:red;
stroke-linejoin:bevel;
}
.nodetext
{
font:
12px sans-serif;
-webkit-user-select:none;
-moze-user-select:none;
stroke-linejoin:bevel;
}
#container{
width:800px;
height:600px;
border:1px
solid gray;
border-radius:5px;
position:relative;
margin:20px;
}
</style>
</head>
<body>
<div
id='container'></div>
<script
type="text/javascript">
function
Topology(ele){
typeof(ele)=='string'
&& (ele=document.getElementById(ele));
var
w=ele.clientWidth,
h=ele.clientHeight,
self=this;
this.force
= d3.layout.force().gravity(.05).distance(200).charge(-800).size([w,
h]);
this.nodes=this.force.nodes();
this.links=this.force.links();
this.clickFn=function(){};
this.vis
= d3.select(ele).append("svg:svg")
.attr("width",
w).attr("height",
h).attr("pointer-events",
"all");
this.force.on("tick",
function(x) {
self.vis.selectAll("g.node")
.attr("transform",
function(d) { return
"translate("
+ d.x + ","
+ d.y + ")";
});
self.vis.selectAll("line.link")
.attr("x1",
function(d) { return
d.source.x; })
.attr("y1",
function(d) { return
d.source.y; })
.attr("x2",
function(d) { return
d.target.x; })
.attr("y2",
function(d) { return
d.target.y; });
});
}
Topology.prototype.doZoom=function(){
d3.select(this).select('g').attr("transform","translate("
+ d3.event.translate + ")"+
"
scale("
+ d3.event.scale + ")");
}
Topology.prototype.addNode=function(node){
this.nodes.push(node);
}
Topology.prototype.addNodes=function(nodes){
if
(Object.prototype.toString.call(nodes)=='[object
Array]'
){
var
self=this;
nodes.forEach(function(node){
self.addNode(node);
});
}
}
Topology.prototype.addLink=function(source,target){
this.links.push({source:this.findNode(source),target:this.findNode(target)});
}
Topology.prototype.addLinks=function(links){
if
(Object.prototype.toString.call(links)=='[object
Array]'
){
var
self=this;
links.forEach(function(link){
self.addLink(link['source'],link['target']);
});
}
}
Topology.prototype.removeNode=function(id){
var
i=0,
n=this.findNode(id),
links=this.links;
while
( i < links.length){
links[i]['source']==n
|| links[i]['target']
==n ? links.splice(i,1)
: ++i;
}
this.nodes.splice(this.findNodeIndex(id),1);
}
Topology.prototype.removeChildNodes=function(id){
var
node=this.findNode(id),
nodes=this.nodes;
links=this.links,
self=this;
var
linksToDelete=[],
childNodes=[];
links.forEach(function(link,index){
link['source']==node
&&
linksToDelete.push(index)
&&
childNodes.push(link['target']);
});
linksToDelete.reverse().forEach(function(index){
links.splice(index,1);
});
var
remove=function(node){
var
length=links.length;
for(var
i=length-1;i>=0;i--){
if
(links[i]['source']
== node ){
var
target=links[i]['target'];
links.splice(i,1);
nodes.splice(self.findNodeIndex(node.id),1);
remove(target);
}
}
}
childNodes.forEach(function(node){
remove(node);
});
for(var
i=nodes.length-1;i>=0;i--){
var
haveFoundNode=false;
for(var
j=0,l=links.length;j<l;j++){
(
links[j]['source']==nodes[i]
|| links[j]['target']==nodes[i]
) && (haveFoundNode=true)
}
!haveFoundNode
&& nodes.splice(i,1);
}
}
Topology.prototype.findNode=function(id){
var
nodes=this.nodes;
for
(var i in nodes){
if
(nodes[i]['id']==id
) return
nodes[i];
}
return
null;
}
Topology.prototype.findNodeIndex=function(id){
var
nodes=this.nodes;
for
(var i in nodes){
if
(nodes[i]['id']==id
) return
i;
}
return
-1;
}
Topology.prototype.setNodeClickFn=function(callback){
this.clickFn=callback;
}
Topology.prototype.update=function(){
var
link = this.vis.selectAll("line.link")
.data(this.links,
function(d) { return
d.source.id + "-"
+ d.target.id; })
.attr("class",
function(d){
return
d['source']['status']
&& d['target']['status']
? 'link'
:'link
link_error';
});
link.enter().insert("svg:line",
"g.node")
.attr("class",
function(d){
return
d['source']['status']
&& d['target']['status']
? 'link'
:'link
link_error';
});
link.exit().remove();
var
node = this.vis.selectAll("g.node")
.data(this.nodes,
function(d) { return
d.id;});
var
nodeEnter = node.enter().append("svg:g")
.attr("class",
"node")
.call(this.force.drag);
var
self=this;
nodeEnter.append("svg:image")
.attr("class",
"circle")
.attr("xlink:href",
function(d){
return
d.expand ? "http://ww2.sinaimg.cn/large/412e82dbjw1dsbny7igx2j.jpg"
: "http://ww4.sinaimg.cn/large/412e82dbjw1dsbnxezrrpj.jpg";
})
.attr("x",
"-32px")
.attr("y",
"-32px")
.attr("width",
"64px")
.attr("height",
"64px")
.on('click',function(d){
d.expand && self.clickFn(d);})
nodeEnter.append("svg:text")
.attr("class",
"nodetext")
.attr("dx",
15)
.attr("dy",
-35)
.text(function(d)
{ return
d.id });
node.exit().remove();
this.force.start();
}
var
topology=new
Topology('container');
var
nodes=[
{id:'10.4.42.1',type:'router',status:1},
{id:'10.4.43.1',type:'switch',status:1,expand:true},
{id:'10.4.44.1',type:'switch',status:1},
{id:'10.4.45.1',type:'switch',status:0}
];
var
childNodes=[
{id:'10.4.43.2',type:'switch',status:1},
{id:'10.4.43.3',type:'switch',status:1}
];
var
links=[
{source:'10.4.42.1',target:'10.4.43.1'},
{source:'10.4.42.1',target:'10.4.44.1'},
{source:'10.4.42.1',target:'10.4.45.1'}
];
var
childLinks=[
{source:'10.4.43.1',target:'10.4.43.2'},
{source:'10.4.43.1',target:'10.4.43.3'},
{source:'10.4.43.2',target:'10.4.43.3'}
]
topology.addNodes(nodes);
topology.addLinks(links);
topology.setNodeClickFn(function(node){
if(!node['_expanded']){
expandNode(node.id);
node['_expanded']=true;
}else{
collapseNode(node.id);
node['_expanded']=false;
}
});
topology.update();
function
expandNode(id){
topology.addNodes(childNodes);
topology.addLinks(childLinks);
topology.update();
}
function
collapseNode(id){
topology.removeChildNodes(id);
topology.update();
}
</script>
</body>
</html>