内容简要:
这次笔记接着上一个笔记继续完善一个force力导向图,上个笔记写完第七点,
这里从第八点开始继续完成这个图表的功能。这个比较代码上和笔记一有些出入,最终的代码
我会上传的。8 在连线上显示箭头方向和对应的说明。
9 替换节点为某种图标。
10 使图标不能拖出显示窗外。
11 使节点不能重叠。
12 实现动态修改数据使其图表发生变化。
13 使其图表可以自适应。
14 缩放force。
8 在连线上显示对应的说明
//1 先绘制一个箭头,这里force_svg就是在body中添加的svg。
force_svg.append('defs').append('marker')
.attr('id','end')
.attr('viewBox','-0 -5 10 10')
.attr('refX',25)
.attr('refY',0)
.attr('orient','auto')
.attr('markerWidth',10)
.attr('markerHeight',10)
.attr('xoverflow','visible')
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#ccc')
.attr('stroke','#ccc');
//2 添加连线并和箭头组合在一起,这里link的data已经设置了。
var linkEnter = link
.enter()
.append("line")
.attr("class","link")
.attr("id",function(d,i) {return 'line'+i})
.attr('marker-end','url(#end)')
.style("stroke","#ccc")
.style("pointer-events", "none");
9 替换节点为某种图标
//nodeEnter 同上面linkEnter一样的来的,这里为每个node里面添加一个image标签并设置图片,坐标和大小
var node_image = nodeEnter.append("image")
.attr("xlink:href", function(d, i) {
if (d.node_node_type == "GROUP") {
return "images/group.png";
} else if (d.node_node_type == "SERVICE") {
return "images/Network_Service.png";
} else if (d.node_node_type == "HOST") {
return "images/server.png";
} else if (d.node_node_type == "INSTANCE") {
return "images/Select_Object_Side.png";
} else {
return null;
}
})
.attr("x", -16)//使图片位于中心位置
.attr("y", -16)
.attr("width", 32)
.attr("height", 32);
//并修改tick事件对应的函数,修改其中
//更新节点坐标
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
现在的界面应该是如下图所示:
10 使图标不能拖出显示窗外
//这里通过修改tick事件的函数实现此功能,在事件函数中添加如下代码
node.attr("cx", function(d) {
return d.x = Math.max(16, Math.min(width_2 - 16, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(16, Math.min(height_2 - 16, d.y));
});
//控制坐标不会大于宽度和高度
11 使节点不能重叠
//节点重叠是因为相互作用力设置得不够,这里通过设置`d3.forceSimulation()`这个函数中
//的`charge`来实现调整节点间的相互作用力,改为如下代码,可修改strength的值来观察图表有什么不同。
.force("charge", d3.forceManyBody().strength(-400))
12 实现动态修改数据使其图表发生变化
步骤:
1 定时获取最新的json数据。
2 对比节点和连线并更新和添加不同或者没有的节点。
3 更新svg中的节点,连线的说明,图片,状态等信息。
4 突出提示警告和异常
1 定时获取新的json
//5秒加载一次,并随机修改一个node的状态
d3.interval(function() {
new_data.nodes[Math.floor(Math.random()*new_data.nodes.length)].node_current_state = Math.floor(Math.random()*4);
reloadData();
}, 5000, d3.now());
function reloadData(){
//请看第二点
}
2 对比节点并更新和添加不同或者没有的节点
//1 定义个来更新数据的对象
function myGraph() {
//---------node------------------------------
this.findNode = function(id) {
for (var i in graph.nodes) {
if (graph.nodes[i]["node_node_id"] === id) return graph.nodes[i];
}
return null;
};
this.findNodeIndex = function(id) {
for (var i = 0; i < graph.nodes.length; i++) {
if (graph.nodes[i].node_node_id == id) {
return i;
}
}
return null;
};
this.removeNodeByIndex = function(index, id) {
var i = graph.links.length;
while (i--) {
if ((graph.links[i]['source'].node_node_id == id) || (graph.links[i]['target'].node_node_id == id)) {
graph.links.splice(i, 1);
}
}
graph.nodes.splice(index, 1);
};
this.clearNode = function(new_nodes) {
var i = this.getNodeLength();
if (new_nodes.length < i) {
while (i--) {
if (!this.findeNodeInArray(new_nodes, graph.nodes[i]["node_node_id"])) {
this.removeNodeByIndex(i, graph.nodes[i]["node_node_id"]);
}
}
}
update();
}
this.getNodeLength = function() {
if (graph && graph.nodes) {
return graph.nodes.length;
}
return 0;
}
this.findeNodeInArray = function(obj_arr, id) {
for (var n in obj_arr) {
if (n["node_node_id"] === id) return n;
}
return null;
};
this.addNode = function(new_node) {
graph.nodes.push( jQuery.extend(true, {}, new_node));
update();
};
this.updateNode = function(new_data_node) {
var index = this.findNodeIndex(new_data_node.node_node_id);
if (index) {
for (var attr in new_data_node) {
if (new_data_node.hasOwnProperty(attr)) graph.nodes[index][attr] = new_data_node[attr];
}
}
update();
}
//---------------link--------------
this.findLink = function(source, target) {
for (var i = 0; i < graph.links.length; i++) {
if (graph.links[i].source.node_node_id == source && graph.links[i].target.node_node_id == target) {
return graph.links[i];
}
}
return null;
}
this.findLinkInArray = function(objArray, link) {
for (var l in objArray) {
if (l.source == link.source.node_node_id && l.target == link.target.node_node_id) {
return l;
}
}
return null;
}
this.clearLink = function(links) {
var i = graph.links.length;
if (links.length < i) {
while(i--){
if (!this.findLinkInArray(links, graph.links[i])) {
graph.links.splice(i, 1);
}
}
}
update();
}
this.removeLink = function(source, target) {
for (var i = 0; i < graph.links.length; i++) {
if (graph.links[i].source.node_node_id == source && graph.links[i].target.node_node_id == target) {
graph.links.splice(i, 1);
break;
}
}
update();
};
this.removeallLinks = function() {
graph.links.splice(0, graph.links.length);
update();
};
this.removeAllNodes = function() {
graph.nodes.splice(0, graph.nodes.length);
update();
};
this.addLink = function(newLink) {
graph.links.push(jQuery.extend(true, {}, newLink));
update();
};
var update = function() {
showForce();
if(parent.iframeLoaded){
parent.iframeLoaded();
}
};
// Make it all go
update();
}
//2 完整的reload函数。对node 和line都是先查找然后根据查找结果来更新或者插入到force的data中
var my_graph;
function reloadData() {
if(!my_graph){
my_graph = new myGraph();
}
//handle node
$.each(new_data.nodes, function(i,new_node) {
var old_node = my_graph.findNode(new_node.node_node_id);
if (!old_node) {
my_graph.addNode(new_node);
} else {
my_graph.updateNode(new_node);
}
});
my_graph.clearNode(new_data.nodes);
//handle line
$.each(new_data.links,function(i,new_link) {
var old_link = my_graph.findLink(new_link.source, new_link.target);
if (!old_link) {
my_graph.addLink(new_link);
} else {
old_link.link_link_type = new_link.link_link_type;
}
});
my_graph.clearLink(new_data.links);
}
3 更新svg中的节点,连线的说明,图片,状态等信息。
/在d3中设置了data属性后返回的就是更新后的数据,这里有一个资料可以比较清楚的了解update ,exit ,enter之间的关系和获得方式。
// 在修改数据后需要修改界面展示方式的都要对update的数据进行重新设置属性使其根据新的状态来展示数据,对删除的节点也就是exit的节点需要remove,对于新增的节点需要使用enter来定义显示方式。如对节点说明的text标签进行操作的代码如下:
//node
node = node.data(graph.nodes/* ,function(d,i){
return d.node_node_id + d.node_node_type + d.node_current_state;
} */);
//remove
node.exit().remove();
............中间代码省略可在上传的代码中查看其余信息更新的方式.............
//update
node.select("text").text(function(d) {
if (d.node_node_type == "INSTANCE") {
return d.node_ins_name;
} else if(d.node_node_type == "HOST") {
return d.node_display_name + "(" + d.node_host_address + ")";
}else{
return d.node_display_name;
}
});
//new
node_text = nodeEnter.append("text")
.style("fill", "black")
.attr("dx", 20)
.attr("dy", 8)
.text(function(d) {
if (d.node_node_type == "INSTANCE") {
return d.node_ins_name;
} else if(d.node_node_type == "HOST") {
return d.node_display_name + "(" + d.node_host_address + ")";
}else{
return d.node_display_name;
}
});
................省略..................
node = nodeEnter.merge(node);//更新后需要合并节点并赋值给节点对象。
4 突出提示警告和异常
//这里使用不断放大颜色不同的圆来表示信息的级别
var node_circle = nodeEnter.append("circle").style("fill","none")
.attr("r",17)
.style("stroke-width","3")
.style("stroke",function(d,i){
if(d.node_node_type == "HOST"){
if(d.node_current_state == 1){
return "yellow";
}else if(d.node_current_state == 2){
return "red";
}else if(d.node_current_state >0){
return "#919191";
}
}else if(d.node_node_type == "SERVICE"){
if(d.node_current_state == 1){
return "yellow";
}else if(d.node_current_state == 2){
return "#FF8888";
}else if(d.node_current_state == 3){
return "red";
}else if(d.node_current_state >0){
return "#919191";
}
}
if(d.node_current_state == null){
return "#919191";
}
})
.style("opacity", function(d,i){
if(d.node_node_type == "SERVICE" || d.node_node_type == "HOST"){
if(d.node_current_state >0 || d.node_current_state== null){
return 1;
}else{
return 0;
}
}else{
return 0;
}
}).transition()
.duration(1000)
.delay(function(d,i){
if(i){
return i*10;
}
return 10;
})
.on("start", function repeat(d) {
d3.active(this)
.attr("r", 17)
.style("opacity",1)
.transition()
.attr("r", 100)
.style("opacity",0)
.transition()
.on("start", repeat);
});
//这里主要是给每个node创建一个空心的圆,并默认设置为完全透明,根据不同的状态设置不同的颜色和透明度,如果有出现异常的节点并循环的播放start事件中的动画,关于start事件必须要在transition()后才能监听
13使其图表可以自适应。
//这里是根据获得父元素的高和宽来实现的,同时监听窗口的变化来改变svg的宽和高
var width_2 = $(window).width();
var height_2;
if(parent){
height_2 = $(parent).height();
}else{
height_2 = $(window).height();
}
$(window).resize(function() {
width_2 = $(window).width();
height_2 = $(window).height();
//console.log("resize");
reloadData();
});
14 缩放force
//缩放只需要在svg上监听zoom事件并添加个g标签即可
var force_svg = d3.select("body").append("div")
.append("svg")
.attr("width", width_2)
.attr("height", height_2)
.style("fill","white")
.call(d3.zoom().scaleExtent([1 / 2, 8]).on("zoom", function () {force_svg.attr("transform", d3.event.transform)})).append("g");
//call后面就是实现缩放的全部代码。[*zoom*.scaleExtent](https://github.com/d3/d3-zoom#zoom_scaleExtent) - 设置缩放范围。
最终图表如下:
此代码比较混乱,多包涵,仅供学习参考。