包含代码点:
- 横向树
- 拖拽及扩大缩放
- 节点样式自定义
- 折叠:https://www.cnblogs.com/wanyong-wy/p/7603123.html
效果:
代码:
//清空SVG中的内容
d3.select("#svg>svg").remove();
var width = $("#svg").width(),
height = 700,
duration = 500,
i = 0;
var tree = d3.layout.tree()
.size([width, height])
.separation(function (a, b) { //获取或设置相邻结点间的间隔(不仅限于兄弟结点)
if(a.depth == 1 && b.depth == 1){
return 3;
}else{
return (a.parent == b.parent ? 1:2);
};
});
//定义布局方向(横向)
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
// 拖拽及扩大缩放
var zoom = d3.behavior.zoom()
.scaleExtent([.5, 3])
.on('zoom', function () {
svg.attr("transform", "translate(" + (d3.event.translate[0]+150) +","+ d3.event.translate[1] + ") scale(" + d3.event.scale + ")");
});
var svg = d3.select("#svg").append("svg")
.call(zoom) //给svg绑定zoom事件
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(150, 0)"); // 将图整体下移,以防止顶部节点被遮挡
//根据数据生成nodes集合
var nodes = tree.nodes(this.analyzeList);
//记录现在的位置
nodes.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
//获取node集合的关系集合
var links = tree.links(nodes);
links.forEach(function (d) {
d.target.y = d.target.y+100;
if(d.source.depth==4 && d.source.children && d.source.children.length>0){ //携带附件
d.target.y = d.target.y+200;
};
});
//为关系集合设置贝塞尔曲线连接
var link = svg.selectAll(".link")
.data(links)
.enter()
.append("path")
.attr("class", "link")
.attr("d", function(d) {
let x0 = d.source.x,
y0 = d.source.y,
x1 = d.target.x,
y1 = d.target.y- 55,
r = y1 - y0;
return `M ${y0},${x0} C ${y0 + r / 2},${x0} ${y0 + r / 2},${x1} ${y1},${x1}`;
});
var node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + (d.y-50) + "," + d.x + ")";
})
.on("click", nodeClick);
// 绘制矩形与文字
node.append("rect")
.attr('width', function(d){
let width = 0;
if(d.depth==0 || d.depth==1){
width = 100;
}else if(d.children && d.children.length>0){
width = 15;
angular.forEach(d.name, function (te) {
if(/[^A-Za-z0-9]/.test(te)){
width += 12;
}else{
width += 6;
};
});
};
return (width>100)?100:width;
})
.attr('height', function(d){
return d.depth==0 || d.depth==1?'40':'14';
})
.attr('y', function(d){
return d.depth==0 || d.depth==1?'-20':'-8';
})
.attr('x', function(d){
return d.depth==0 || d.depth==1?'-30':'0';
})
.attr('ry', 5)
.attr('rx', 5)
.attr('fill', function(d){
let fill = '';
if(d.depth==0){
fill = '#e04a4a';
}else if(d.depth==1){
fill = '#c79329';
}else{
fill = 'white';
};
return fill;
})
.attr('stroke', function(d){
let stroke = '';
if(d.depth==0){
stroke = 'red';
}else if(d.depth==1){
stroke = '#a57107';
}else{
stroke = 'white';
};
return stroke;
})
.attr('strokeWidth', '2px');
node.append("circle")
.attr("r", 4.5)
.attr('fill', function(d){
let fill = '';
if(d.depth==0 || d.depth==1){
fill = 'none';
}else if(d.click){
fill = $rootScope.theme.chartColor || '#0073BA';
}else{
fill = '#faaa65';
};
return fill;
})
.attr('stroke', function(d){
let stroke = '';
if(d.depth==0 || d.depth==1){
stroke = 'none';
}else if(d.click){
stroke = $rootScope.theme.chartColor || '#0073BA';
}else{
stroke = '#faaa65';
};
return stroke;
})
.attr('strokeWidth', function(d){
return d.depth==0 || d.depth==1?'0':'2px';
});
node.append("text")
.attr('y', function(d){
return d.depth==0 || d.depth==1?'5':'4';
})
.attr("x", function (d) {
return d.depth==0 || d.depth==1?'20':'10';
})
.attr("text-anchor", function(d){
return d.depth==0 || d.depth==1?'middle':'start';
})
.attr('fill', function(d){
return d.depth==0 || d.depth==1?'white':'';
})
.text(function (d) {
let text = "";
if(d.children && d.name.length>7){
text = d.name.slice(0,7)+'...';
}else{
text = d.name+(d.key?":":'');
};
if(d.click && (d.name == d.key)){
text = "";
};
if(d.parent && d.parent.name == "hash值"){
text = d.name.slice(0,10) + "...";
};
if(d.name == "不同样本Dropper相同文件"){
text = d.name.slice(0,7) + "...";
};
if(d.children){
text += ' ['+d.children.length+']';
};
return text;
})
.append("a")
.attr("class", function (d) {
return d.click?"click":"noclick";
})
.attr("fill", function(d){
return d.click?($rootScope.theme.chartColor || '#0073BA'):'';
})
.text(function (d) {
return d.key;
})
.on("click", function (d) {
d3.event.stopPropagation();
if(d.type == 'ip' || d.type == 'domain'){
$scope.sideBox.query(d3.event, d.key);
};
});
node.append("title")
.text(function (d) {
let title = d.name;
if(d.allName) title = d.allName;
if(d.key && d.key != d.name) title = d.name+":"+d.key;
return title;
});
// 节点点击,隐藏或显示子节点
function nodeClick(d){
if(d.children || d._children){
if (d.children){
d._children = d.children;
d.children = null;
}else{
d.children = d._children;
d._children = null;
};
update(d);
};
};
//更新布局
function update(source) {
//计算新树的布局
var nodes = tree.nodes(self.analyzeList).reverse(),
links = tree.links(nodes);
//树的深度这里树d.y。树的宽度最大720,要分四层,所以每层就乘180
nodes.forEach(function(d) {
d.y = d.depth * 180;// 树的x,y倒置了,所以这里Y其实是横向的
});
links.forEach(function (d) {
d.target.y = d.target.y+100;
if(d.source.depth==4 && d.source.children && d.source.children.length>0){ //携带附件
d.target.y = d.target.y+200;
};
});
//数据连接,根据id绑定数据
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id //最初新点开的节点都没有id
|| (d.id = ++i); //为没有id的节点添加上ID
});
//点击时增加新的子节点
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", nodeClick);
// 绘制矩形与文字
nodeEnter.append("rect")
.attr('width', function(d){
let width = 0;
if(d.depth==0 || d.depth==1){
width = 100;
}else if(d.children && d.children.length>0){
width = 15;
angular.forEach(d.name, function (te) {
if(/[^A-Za-z0-9]/.test(te)){
width += 12;
}else{
width += 6;
};
});
};
return (width>100)?100:width;
})
.attr('height', function(d){
return d.depth==0 || d.depth==1?'40':'14';
})
.attr('y', function(d){
return d.depth==0 || d.depth==1?'-20':'-8';
})
.attr('x', function(d){
return d.depth==0 || d.depth==1?'-30':'0';
})
.attr('ry', 5)
.attr('rx', 5)
.attr('fill', function(d){
let fill = '';
if(d.depth==0){
fill = '#e04a4a';
}else if(d.depth==1){
fill = '#c79329';
}else{
fill = 'white';
};
return fill;
})
.attr('stroke', function(d){
let stroke = '';
if(d.depth==0){
stroke = 'red';
}else if(d.depth==1){
stroke = '#a57107';
}else{
stroke = 'white';
};
return stroke;
})
.attr('strokeWidth', '2px');
nodeEnter.append("circle")
.attr("r", 4.5)
.attr('fill', function(d){
let fill = '';
if(d.depth==0 || d.depth==1){
fill = 'none';
}else if(d.click){
fill = $rootScope.theme.chartColor || '#0073BA';
}else{
fill = '#faaa65';
};
return fill;
})
.attr('stroke', function(d){
let stroke = '';
if(d.depth==0 || d.depth==1){
stroke = 'none';
}else if(d.click){
stroke = $rootScope.theme.chartColor || '#0073BA';
}else{
stroke = '#faaa65';
};
return stroke;
})
.attr('strokeWidth', function(d){
return d.depth==0 || d.depth==1?'0':'2px';
});
nodeEnter.append("text")
.attr('y', function(d){
return d.depth==0 || d.depth==1?'5':'4';
})
.attr("x", function (d) {
return d.depth==0 || d.depth==1?'20':'10';
})
.attr("text-anchor", function(d){
return d.depth==0 || d.depth==1?'middle':'start';
})
.attr('fill', function(d){
return d.depth==0 || d.depth==1?'white':'';
})
.text(function (d) {
let text = "";
if(d.children && d.name.length>7){
text = d.name.slice(0,7)+'...';
}else{
text = d.name+(d.key?":":'');
};
if(d.click && (d.name == d.key)){
text = "";
};
if(d.parent && d.parent.name == "hash值"){
text = d.name.slice(0,10) + "...";
};
if(d.name == "不同样本Dropper相同文件"){
text = d.name.slice(0,7) + "...";
};
if(d.children){
text += ' ['+d.children.length+']';
};
return text;
})
.append("a")
.attr("class", function (d) {
return d.click?"click":"noclick";
})
.attr("fill", function(d){
return d.click?($rootScope.theme.chartColor || '#0073BA'):'';
})
.text(function (d) {
return d.key;
})
.on("click", function (d) {
d3.event.stopPropagation();
if(d.type == 'ip' || d.type == 'domain'){
$scope.sideBox.query(d3.event, d.key);
};
});
nodeEnter.append("title")
.text(function (d) {
let title = d.name;
if(d.allName) title = d.allName;
if(d.key && d.key != d.name) title = d.name+":"+d.key;
return title;
});
//原有节点更新到新位置
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
//折叠节点的子节点收缩回来
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
//数据连接,根据目标节点的id绑定数据
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
//增加新连接
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
//原有连接更新位置
link.transition()
.duration(duration)
.attr("d", diagonal);
//折叠的链接,收缩到源节点处
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// 把旧位置存下来,用以过渡
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
};