D3.js-v4.*学习笔记二(完成一个force力导向图)

内容简要:

这次笔记接着上一个笔记继续完善一个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 + ")";
                });

现在的界面应该是如下图所示:

Paste_Image.png

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) - 设置缩放范围。

最终图表如下:

Paste_Image.png

此代码比较混乱,多包涵,仅供学习参考。

百度网盘代码下载地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容