How to create d3.js Collapsible force layout with non tree data?

前端 未结 2 1631
时光取名叫无心
时光取名叫无心 2021-01-14 04:43

I have a d3 force directed layout with data in a similar structure below. Is it possible to apply collapsible force layout such as http://bl.ocks.org/mbostock/1062288 to it?

相关标签:
2条回答
  • 2021-01-14 05:04

    If I understand correctly, perhaps this is what you are looking for. I edited the demo you linked to. Now when a source node is collapsed, we iterate over all edges and look for other nodes it has edges to.

    for each target node that the source node has an edge to, we increment it's collapsing count. If a node has a collapsing count of greater than zero, it is not displayed.

    When we uncollapse a node, we do the same thing, except we decrement from the collapsing count.

    We need this collapsing count, since, as we are not in a tree, nodes can have more than one node which should cause them to collapse.

    I made this work for directed graphs, though I'm not sure that's what you wanted.

    Let me know what you think!

    The json I used:

      {
        "nodes": [
         {"x": 469, "y": 410},
         {"x": 493, "y": 364},
         {"x": 442, "y": 365},
         {"x": 467, "y": 314}
     ],
         "links": [
          {"source":  0, "target":  1},
          {"source":  1, "target":  2},
          {"source":  2, "target":  0},
          {"source":  1, "target":  3},
          {"source":  3, "target":  2}
      ]
     }
    

    Modified tutorial code:

    <!DOCTYPE html>
    <meta charset="utf-8">
    <title>Force-Directed Graph</title>
    <style>
    
    .node {
      cursor: pointer;
      stroke: #3182bd;
      stroke-width: 1.5px;
    }
    
    .link {
      fill: none;
      stroke: #9ecae1;
      stroke-width: 1.5px;
    }
    
    </style>
    <body>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script>
    
    var width = 960,
        height = 500,
        root;
    
    var force = d3.layout.force()
        .size([width, height])
        .on("tick", tick);
    
    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);
    
    //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", 4)
        .attr("markerHeight", 4)
        .attr("orient", "auto")
        .append("path")
        .attr("d", "M0,-5L10,0L0,5");
    
    var link = svg.selectAll(".link"),
        node = svg.selectAll(".node");
    
    d3.json("graph.json", function(json) {
      root = json;
      //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];
        link.target = root.nodes[link.target];
        link.id = i;
      }
    
      update();
    });
    
    function update() {
      //Keep only the visible nodes
      var nodes = root.nodes.filter(function(d) {
        return d.collapsing == 0;
      });
      var links = root.links;
      //Keep only the visible links
      links = root.links.filter(function(d) {
        return d.source.collapsing == 0 && d.target.collapsing == 0;
      });
    
      force
          .nodes(nodes)
          .links(links)
          .start();
    
      // Update the links…
      link = link.data(links, function(d) { return d.id; });
    
      // Exit any old links.
      link.exit().remove();
    
      // Enter any new links.
      link.enter().insert("line", ".node")
          .attr("class", "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; })
          .attr("marker-end", "url(#arrow)");
    
      // Update the nodes…
      node = node.data(nodes, function(d){ return d.id; }).style("fill", color);
    
      // Exit any old nodes.
      node.exit().remove();
    
      // Enter any new nodes.
      node.enter().append("circle")
          .attr("class", "node")
          .attr("cx", function(d) { return d.x; })
          .attr("cy", function(d) { return d.y; })
          .attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
          .style("fill", color)
          .on("click", click)
          .call(force.drag);
    }
    
    function tick() {
      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; });
    }
    
    // Color leaf nodes orange, and packages white or blue.
    function color(d) {
      return d.collapsed ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
    }
    
    // Toggle children on click.
    function click(d) {
      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();
    }
    
    </script>
    
    0 讨论(0)
  • 2021-01-14 05:04

    Try this:

        var width = 960,height = 500;
    
        var force = d3.layout.force().size([width, height]).charge(-400)
                    .linkDistance(40)
                    .on("tick", tick);
    
             var drag = force.drag().on("dragstart", dragstart);
    
               var svg = d3.select("body").append("svg").attr("width", width)
                            .attr("height", height);
    
               var link = svg.selectAll(".link"),
                      node = svg.selectAll(".node");
    
                d3.json("graph.json", function(error, graph) {
                         force.nodes(graph.nodes).links(graph.links)
                             .start();
    
            link = link.data(graph.links).enter().append("line")
                          .attr("class", "link");
    
                      node = node.data(graph.nodes)
                     .enter().append("circle")
                     .attr("class", "node")
                     .attr("r", 12)
                     .call(drag);
             });
    
             function tick() {
                  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; });
               }
    
    
    
               function dragstart(d) {
                      d3.select(this).classed("fixed", d.fixed = true);
                  }
    

    You should use json file like this:

    graph.json

          {
            "nodes": [
             {"x": 469, "y": 410},
             {"x": 493, "y": 364},
             {"x": 442, "y": 365},
             {"x": 467, "y": 314},
         ],
             "links": [
              {"source":  0, "target":  1},
              {"source":  1, "target":  2},
              {"source":  2, "target":  0},
              {"source":  1, "target":  3},
              {"source":  3, "target":  2},
          ]
         }
    
    0 讨论(0)
提交回复
热议问题