Javascript 如何使用具有d3上下文菜单的非树数据创建d3.js可折叠力布局图?

Javascript 如何使用具有d3上下文菜单的非树数据创建d3.js可折叠力布局图?,javascript,d3.js,Javascript,D3.js,我有一个d3力定向布局图,类似于。我在添加可折叠功能时遇到了一些问题,我打算在右键单击节点时从菜单调用该功能。有什么想法吗? 下面请找到我修改过的代码 <!DOCTYPE html> <meta charset="utf-8"> <title> Collapsible Force Layout Graph using non-tree data with a d3-context-menu

我有一个d3力定向布局图,类似于。我在添加可折叠功能时遇到了一些问题,我打算在右键单击节点时从菜单调用该功能。有什么想法吗? 下面请找到我修改过的代码

    <!DOCTYPE html>
        <meta charset="utf-8">
        <title>
        Collapsible Force Layout Graph using non-tree data with a d3-context-menu
        </title>
        <head>
        <script data-require="d3@3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
        <link href="css/style.css" rel="stylesheet" />
        <script src="js_script/d3-context-menu.js"></script>  
        </head>
        <body>
        <script language="JavaScript">
        var width  = 1300,
            height =  700;
        var h = 2,
            w = 4;

        var focus_node = null, highlight_node = null;
        var focus_node = null, highlight_node = null;

        var text_center = false;
        var outline = false;

        var min_score = 0;
        var max_score = 1;

        var color = d3.scale.category20();

        var highlight_color = "#666";
        var highlight_trans = 0.1;

        var size = d3.scale.pow().exponent(1)
          .domain([1,100])
          .range([8,24]);

        var force = d3.layout.force()
            .size([width, height])
            .charge(-400)
            .linkDistance(40)
          .size([w,h]);

          var drag = force.drag()
            .on("dragstart", dragstart);

        var default_node_color = "#ccc";
        var default_link_color = "#666";
        var nominal_base_node_size = 8;
        var nominal_text_size = 10;
        var max_text_size = 24;
        var nominal_stroke = 1.6;
        var max_stroke = 1.6*7;
        var max_base_node_size = 36;
        var min_zoom = 0.1;
        var max_zoom = 7;

        var menu = [
                    {
                        title: 'Collapes Nodes',
                        action: function(elm, d, i) {
                             if (!d3.event.defaultPrevented) {
            //check if link is from this node, and if so, collapse
            root.links.forEach(function(l) {
              if(l.source.id == d.id) {
                if(d.collapsed){
                  l.target.collapsing--;
                } else {
                  l.target.collapsing++;
                }
              }
            });
            d.collapsed = !d.collapsed;
          }
          update();
                        }
                    },
                    {
                        title: 'Expand Nodes',
                        action: function(elm, d, i) {
                            if (!d3.event.defaultPrevented) {
            //check if link is from this node, and if so, collapse
            root.links.forEach(function(l) {
              if(l.source.id == d.id) {
                if(d.collapsed){
                  l.target.collapsing--;
                } else {
                  l.target.collapsing++;
                }
              }
            });
            d.collapsed = !d.collapsed;
          }
          update();
                        }
                    },
                    {
                        title: 'Release Node',
                        action: function(elm, d, i) {
                            d3.select(this).classed("fixed", d.fixed = false);
                        }
                    }
                ]

        var svg = d3.select("body").append("svg");
        var zoom = d3.behavior.zoom().scaleExtent([min_zoom,max_zoom])
        var g = svg.append("g");
        svg.style("cursor","move");

        d3.json("data/risk_final-2.json", function(error, graph) {

        root = graph;
          //Give nodes ids and initialize variables
          for(var i=0; i<root.nodes.length; i++) {
            var node = root.nodes[i];
            node.id = i;
            node.collapsing = 0;
            node.collapsed = false;
          }

          //Give links ids and initialize variables
          for(var i=0; i<root.links.length; i++) {
            var link = root.links[i];
            link.source = root.nodes[link.source] || (nodes[link.source] = {name: link.source});
            link.target = root.nodes[link.target] || (nodes[link.target] = {name: link.target});
            link.id = i;
          }

         // update();

          force
            .nodes(graph.nodes)
            .links(graph.links)
            .start();

            //Added markers to indicate that this is a directed graph
        svg.append("defs").selectAll("marker")
            .data(["arrow"])
            .enter().append("marker")
            .attr("id", function(d) { return d; })
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 15)
            .attr("refY", -1.5)
            .attr("markerWidth", 2)
            .attr("markerHeight", 2)
            .attr("orient", "auto")
            .append("path")
            .attr("d", "M0,-5L10,0L0,5");


          var link = g.selectAll(".link")
            .data(graph.links)
          // .data(force.links())
            .enter().append("line")
            .attr("class", "link")
            .attr("stroke-opacity", function(d) { if(d.label == 'is a') { return '0.8';} else{ return '0.2'; }; })
            .style("stroke", function(d) { if(d.color !== null) { return d.color;}; })
            .style("stroke-width", function(d) { return d.value; })
            .style("stroke", function(d) { 
            if (isNumber(d.group) && d.group>=0) return color(d.group);
            else return default_link_color; })

            .html(function(d) {
            return " Link : " + d.source.name + " -> " + d.target.name;
          })

             // action to take on mouseover over edges
                    .on("mouseover", function(d, i){d3.select(this)
                    .style("stroke", "#666")
                    .style("stroke-width", function(d) { return d.value*2;})
                    .attr("stroke-opacity", "1.0");})

                    // action to take on mouseout over edges
                    .on("mouseout", function(d, i){d3.select(this)
                    .style("stroke", function(d) { if(d.color !== null) { return d.color;}; })
                    .style("stroke-width", function(d) { return d.value; })
                    .attr("stroke-opacity", function(d) { if(d.label == 'is a') { return '0.8';} else { return '0.2'; };}) })

                   // Assign Id's to your path/Link
                   .attr("class", function(d) { return "link " + d.type; })
                   .attr("id",function(d,i) { return "linkId_" + i; })

               // Displays the markers to indicate the link direction
              .attr("marker-end", "url(#arrow)")      
              .call(force.drag);

        //Use SVG textPath element for associating labels with above links
            var linktext = svg.append("svg:g").selectAll("g.linklabelholder").data(force.links());

            linktext.enter().append("g").attr("class", "linklabelholder")
             .append("text")
             .attr("class", "linklabel")
             .style("font-size", "13px")
             .attr("x", "50")
             .attr("y", "-20")
             .attr("text-anchor", "start")
             .style("fill","#000")
             .append("textPath")
             .attr("xlink:href",function(d,i) { return "#linkId_" + i;})
             .text(function(d) { 
             return d.value; 
             });

        //Show tool-tip on links 
            link.append("title")
            .html(function(d) {
            return " Link : " + d.source.name + " -> " + d.target.name +  "<br/>" + "Link Value : " + d.value;
          })

          var node = g.selectAll("circle.node")
            .data(graph.nodes)
            //.data(force.nodes())
            .enter().append("g")
            .attr("class", "node")
            .attr("r", function(d) { return d.size; })
            .call(force.drag)
            .on('contextmenu', d3.contextMenu(menu))


            node.on("dblclick.zoom", function(d) { d3.event.stopPropagation();
            var dcx = (window.innerWidth/2-d.x*zoom.scale());
            var dcy = (window.innerHeight/2-d.y*zoom.scale());
            zoom.translate([dcx,dcy]);
             g.attr("transform", "translate("+ dcx + "," + dcy  + ")scale(" + zoom.scale() + ")");

            });
            //add the tooltip
          node.append("title")
                                                             // line break ->.html
              .html(function(d) {return "Name : " + d.name  +  "<br/>" + "Group : " + d.group + "<br/>" + "Type : " + d.type +   "<br/>" + "Size : " + d.size;});


            var tocolor = "fill";
            var towhite = "stroke";
            if (outline) {
                tocolor = "stroke"
                towhite = "fill"
            }

    var circle = node.append("path")
                .attr("d", d3.svg.symbol()
                .size(function(d) { return Math.PI*Math.pow(size(d.size)||nominal_base_node_size,2); })
                .type(function(d) { return d.type; }))
                .call(drag)
                .style(tocolor, function(d) { 
    if (isNumber(d.group) && d.group>=0) return color(d.group);
            else return default_node_color; })
                .style("stroke-width", nominal_stroke)
                .style(towhite, "#ccc");

          var text = g.selectAll(".text")
            .data(graph.nodes)
          //  .data(force.nodes())
            .enter().append("text")
            .attr("dy", ".35em")
            .style("font-size", nominal_text_size + "px")

            if (text_center)
             text.text(function(d) { return d.name; })
            .style("text-anchor", "middle");
            else 
            text.attr("dx", function(d) {return (size(d.size)||nominal_base_node_size);})
            .text(function(d) { return '\u2002'+d.name; });

            node.on("mouseover", function(d) {
            set_highlight(d);

              //highlight 1st-order links/edges
        link.classed("link-active", function(o) {
                                    return o.source === d || o.target === d ? true : false;
                                });
            })
          .on("mousedown", function(d) { d3.event.stopPropagation();
            focus_node = d;
            set_focus(d)
            if (highlight_node === null) set_highlight(d)

        }   ).on("mouseout", function(d) {
                exit_highlight();

              // Hides the highlight 1st-order links/edges
        link.classed("link-active", function(o) {
                                    return false;
                                });
        });

    d3.select(window).on("mouseup",  
                function() {
                if (focus_node!==null)
                {
                    focus_node = null;
                    if (highlight_trans<1)
                    {

                circle.style("opacity", 1);
              text.style("opacity", 1);
              link.style("opacity", 1); 
            }
                }

            if (highlight_node === null) exit_highlight();
                });

        function exit_highlight()
        {
                highlight_node = null;
            if (focus_node===null)
            {
                svg.style("cursor","move");
                if (highlight_color!="#ccc")
            {
              circle.style(towhite, "#ccc");
              text.style("font-weight", "normal");
             link.style("stroke", function(o) {return (isNumber(o.group) && o.group>=0)?color(o.group):default_link_color});
         }
    }
    }

        function set_focus(d)
        {   
        if (highlight_trans<1)  {
            circle.style("opacity", function(o) {
                        return isConnected(d, o) ? 1 : highlight_trans;
                    });

                    text.style("opacity", function(o) {
                        return isConnected(d, o) ? 1 : highlight_trans;
                    });

                    link.style("opacity", function(o) {
                        return o.source.index == d.index || o.target.index == d.index ? 1 : highlight_trans;
                    });     
            }
        }

    function set_highlight(d)
        {
            svg.style("cursor","pointer");
            if (focus_node!==null) d = focus_node;
            highlight_node = d;

            if (highlight_color!="#ccc")
            {
                  circle.style(towhite, function(o) {
                        return isConnected(d, o) ? highlight_color : "#ccc";});
                    text.style("font-weight", function(o) {
                        return isConnected(d, o) ? "bold" : "normal";});
                    link.style("stroke", function(o) {
                      return o.source.index == d.index || o.target.index == d.index ? highlight_color : ((isNumber(o.group) && o.group>=0)?color(o.group):default_link_color);

                    });
            }
        }

    zoom.on("zoom", function() {

            var stroke = nominal_stroke;
            if (nominal_stroke*zoom.scale()>max_stroke) stroke = max_stroke/zoom.scale();

           // link/edge width adjust based on zoom-in and zoom-out
            link.style("stroke", function(d) { if(d.color !== null) { return d.color;}; })
          circle.style("stroke-width",stroke);

            var base_radius = nominal_base_node_size;
            if (nominal_base_node_size*zoom.scale()>max_base_node_size) base_radius = max_base_node_size/zoom.scale();
                circle.attr("d", d3.svg.symbol()
                .size(function(d) { return Math.PI*Math.pow(size(d.size)*base_radius/nominal_base_node_size||base_radius,2); })
                .type(function(d) { return d.type; }))


            if (!text_center) text.attr("dx", function(d) { return (size(d.size)*base_radius/nominal_base_node_size||base_radius); });

            var text_size = nominal_text_size;
            if (nominal_text_size*zoom.scale()>max_text_size) text_size = max_text_size/zoom.scale();
            text.style("font-size",text_size + "px");

            g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
            });

          svg.call(zoom);     

          resize();

          d3.select(window).on("resize", resize).on("keydown", keydown);

          force.on("tick", function() {

            node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
            text.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

            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; });

            node.attr("cx", function(d) { return d.x; })
              .attr("cy", function(d) { return d.y; });
            });

    var linkedByIndex = {};
            graph.links.forEach(function(d) {
            linkedByIndex[d.source + "," + d.target] = true;
            });

    function isConnected(a, b) {
                return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
            }

    function hasConnections(a) {
                for (var property in linkedByIndex) {
                        s = property.split(",");
                        if ((s[0] == a.index || s[1] == a.index) && linkedByIndex[property])                    return true;
                }
            return false;
            }

    function resize() {
            var width = window.innerWidth, height = window.innerHeight;
            svg.attr("width", width).attr("height", height);

            force.size([force.size()[0]+(width-w)/zoom.scale(),force.size()[1]+(height-h)/zoom.scale()]).resume();
            w = width;
            h = height;
            }

    function keydown() {
            if (d3.event.keyCode==32) {  force.stop();}
            else if (d3.event.keyCode>=48 && d3.event.keyCode<=90 && !d3.event.ctrlKey && !d3.event.altKey && !d3.event.metaKey)
            {
          switch (String.fromCharCode(d3.event.keyCode)) {
            case "C": keyc = !keyc; break;
            case "S": keys = !keys; break;
            case "T": keyt = !keyt; break;
            case "R": keyr = !keyr; break;
            case "X": keyx = !keyx; break;
            case "D": keyd = !keyd; break;
            case "L": keyl = !keyl; break;
            case "M": keym = !keym; break;
            case "H": keyh = !keyh; break;
            case "1": key1 = !key1; break;
            case "2": key2 = !key2; break;
            case "3": key3 = !key3; break;
            case "0": key0 = !key0; break;
          }

    link.style("display", function(d) {
                        var flag  = vis_by_type(d.source.type)&&vis_by_type(d.target.type)&&vis_by_node_group(d.source.group)&&vis_by_node_group(d.target.group)&&vis_by_link_group(d.group);
                        linkedByIndex[d.source.index + "," + d.target.index] = flag;
                      return flag?"inline":"none";});
    node.style("display", function(d) {
                        return (key0||hasConnections(d))&&vis_by_type(d.type)&&vis_by_node_group(d.group)?"inline":"none";});
          text.style("display", function(d) {
                        return (key0||hasConnections(d))&&vis_by_type(d.type)&&vis_by_node_group(d.group)?"inline":"none";});

    if (highlight_node !== null)
                        {
                            if ((key0||hasConnections(highlight_node))&&vis_by_type(highlight_node.type)&&vis_by_node_group(highlight_node.group)) { 
                            if (focus_node!==null) set_focus(focus_node);
                            set_highlight(highlight_node);
                            }
                            else {exit_highlight();}
    }

        }   
        }


        });

function vis_by_node_group(group)
        {
            if (isNumber(group))
            {
            if (group>=0.666) return keyh;
            else if (group>=0.333) return keym;
            else if (group>=0) return keyl;
            }
            return true;
        }

 function vis_by_link_group(group)
        {
            if (isNumber(group))
            {
            if (group>=0.666) return key3;
            else if (group>=0.333) return key2;
            else if (group>=0) return key1;
        }
            return true;
        }

        function isNumber(n) {
          return !isNaN(parseFloat(n)) && isFinite(n);
        }   
        //Fixed property of the dragged node is set to true. 
        //This prevents the force layout from subsequently changing the position of the node (due to forces)
        function dragstart(d) {
          d3.select(this).classed("fixed", d.fixed = true);
        }
        </script>
        </body>
        </html>
 {
    "nodes": [{
        "name": "WesternAustralia",
        "group": "0.8",
        "size": "7",
        "type": "circle"
    }, {
        "name": "NewGuinea",
        "group": "0.2",
        "size": "7",
        "type": "circle"
    }, {
        "name": "EasternAustralia",
        "group": "0.8",
        "size": "17",
        "type": "circle"
    }, {
        "name": "Indonesia",
        "group": "0.8",
        "size": "16",
        "type": "circle"
    }, {
        "name": "NorthernEurope",
        "group": "1",
        "size": "12",
        "type": "square"
    }, {
        "name": "SouthernEurope",
        "group": "0.8",
        "size": "9",
        "type": "square"
    }, {
        "name": "WesternEurope",
        "group": "0.7",
        "size": "8",
        "type": "square"
    }, {
        "name": "East_Afrika",
        "group": "0",
        "size": "19",
        "type": "square"
    }, {
        "name": "Egypt",
        "group": "0.4",
        "size": "5",
        "type": "square"
    }, {
        "name": "Madagasca",
        "group": "0.2",
        "size": "3",
        "type": "circle"
    }, {
        "name": "Congo",
        "group": "0.3",
        "size": "4",
        "type": "square"
    }, {
        "name": "South_Afrika",
        "group": "0.4",
        "size": "5",
        "type": "square"
    }, {
        "name": "North_Afrika",
        "group": "1",
        "size": "12",
        "type": "square"
    }, {
        "name": "brazil",
        "group": "0.7",
        "size": "7",
        "type": "square"
    }, {
        "name": "argentina",
        "group": "0.6",
        "size": "6",
        "type": "square"
    }, {
        "name": "peru",
        "group": "0.4",
        "size": "4",
        "type": "square"
    }, {
        "name": "venezuela",
        "group": "0.7",
        "size": "7",
        "type": "square"
    }],
    "links": [{
        "source": 1,
        "target": 0,
        "value": 1
    }, {
        "source": 2,
        "target": 0,
        "value": 2
    }, {
        "source": 2,
        "target": 1,
        "value": 3
    }, {
        "source": 1,
        "target": 3,
        "value": 4
    }, {
        "source": 0,
        "target": 3,
        "value": 5
    }, {
        "source": 4,
        "target": 3,
        "value": 1
    }, {
        "source": 6,
        "target": 5,
        "value": 2
    }, {
        "source": 7,
        "target": 5,
        "value": 3
    }, {
        "source": 7,
        "target": 8,
        "value": 4
    }, {
        "source": 5,
        "target": 8,
        "value": 5
    }, {
        "source": 9,
        "target": 8,
        "value": 1
    },  {
        "source": 29,
        "target": 28,
        "value": 5
    }, {
        "source": 26,
        "target": 30,
        "value": 5
    }, {
        "source": 29,
        "target": 30,
        "value": 5
    }, {
        "source": 28,
        "target": 31,
        "value": 5
    }, {
        "source": 30,
        "target": 31,
        "value": 5
    }, {
        "source": 29,
        "target": 31,
        "value": 5
    }, {
        "source": 15,
        "target": 32,
        "value": 5
    }, {
        "source": 33,
        "target": 32,
        "value": 5
    }, {
        "source": 15,
        "target": 33,
        "value": 5
    }, {
        "source": 30,
        "target": 33,
        "value": 5
    }, {
        "source": 32,
        "target": 34,
        "value": 5
    }, {
        "source": 32,
        "target": 35,
        "value": 5
    }, {
        "source": 36,
        "target": 35,
        "value": 5
    }, {
        "source": 30,
        "target": 37,
        "value": 5
    }, {
        "source": 31,
        "target": 37,
        "value": 5
    }, {
        "source": 32,
        "target": 37,
        "value": 5
    }, {
        "source": 33,
        "target": 37,
        "value": 5
    }, {
        "source": 35,
        "target": 37,
        "value": 5
    }, {
        "source": 34,
        "target": 36,
        "value": 5
    }, {
        "source": 37,
        "target": 38,
        "value": 5
    }, {
        "source": 39,
        "target": 38,
        "value": 5
    }, {
        "source": 41,
        "target": 40,
        "value": 5
    }, {
        "source": 39,
        "target": 40,
        "value": 5
    }, {
        "source": 25,
        "target": 41,
        "value": 0.5
    }, {
        "source": 38,
        "target": 41,
        "value": 2
    }]
}