Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
826 views
in Technique[技术] by (71.8m points)

force layout - How can I auto-zoom my Dagre graph after dynamically adding a node?

My JsFiddle uses the excllent Dagre to dynamcially create a network graph. Full code below, to satisy posting requirements.

My problem is that as I add nodes, I cannot get it to auto-zoom to fit the nodes onto the (SVG canvas of) the graph.

enter image description here

Add a few nodes, and ...

enter image description here

How can I auto-zoom to fit after adding a new node?

Hmmmm, after a certain level of zomming, it might become difficult to read, so I suppose that I ought to limit zooming after a certain level & leave it to user scrolling, with not all of the graph visible at all Times(?).

If only Dagre handled this (and auto-layout (I would strongly prefer to centre one node, lock it in the centre and have the other nodes appear around it - I don't care about ordering, but some form of weighting, whilw not strictly necessary, wouldn't hurt)). Thinks, maybe I am using the wrong library, but searching, and even a quarion on S/W reccomendations didn't help, and Dagre is otherwise excellent.

Anyhoo, I digress. And idea how to get it to auto-zoom?


Here's the HTML:

<!doctype html>
<headd>
<meta charset="utf-8">
<title>Dynamic social network</title>

<link rel="stylesheet" href="style.css">
<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>
<script src="https://dagrejs.github.io/project/dagre-d3/latest/dagre-d3.js"></script>
</head>

<body>

<!-- based loosely on https://dagrejs.github.io/project/dagre-d3/latest/demo/clusters.html -->
<!-- docs at https://npmdoc.github.io/node-npmdoc-dagre/build/apidoc.html-->

<h1>Social network demo</h1>

<style id="css">
.clusters rect {
  fill: whitesmoke;
  stroke: #999;
  stroke-width: 1.5px;
  margin: auto;
}

text {
  font-weight: 300;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
  font-size: 14px;
}

.node rect {
  stroke: #999;
  fill: #fff;
  stroke-width: 1.5px;
}

.edgePath path {
  stroke: #333;
  stroke-width: 1.5px;
}
</style>

<a href="javascript:AddGroup()">Add a group</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="javascript:AddPerson()">Add a person (to a random group, at a random level)</a>
<br>
<br>

<svg id="svg-canvas" width=600 height=6000></svg>

<script id="js">
  // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  let forenames = [];
  forenames.push("Michael");
  forenames.push("David");
  forenames.push("John");
  forenames.push("James");
  forenames.push("Robert");
  forenames.push("Mark");
  forenames.push("William");
  forenames.push("Richard");
  forenames.push("Thomas");
  forenames.push("Jeffrey");
  forenames.push("Steven");
  forenames.push("Joseph");
  forenames.push("Timothy");
  forenames.push("Kevin");
  forenames.push("Scott");
  forenames.push("Brian");
  forenames.push("Charles");
  forenames.push("Paul");
  forenames.push("Daniel");
  forenames.push("Christopher");
  forenames.push("Anthony");
  forenames.push("Kenneth");
  forenames.push("Gregory");
  forenames.push("Ronald");
  forenames.push("Donald");
  forenames.push("Lisa");
  forenames.push("Mary");
  forenames.push("Susan");
  forenames.push("Karen");
  forenames.push("Margaret");
  forenames.push("Patricia");
  forenames.push("Linda");
  forenames.push("Donna");
  forenames.push("Michelle");
  forenames.push("Cynthia");
  forenames.push("Sandra");
  forenames.push("Deborah");
  forenames.push("Tammy");
  forenames.push("Pamela");
  forenames.push("Christine");
  forenames.push("Laura");
  forenames.push("Elizabeth");
  forenames.push("Julie");
  forenames.push("Brenda");
  forenames.push("Jennifer");
  forenames.push("Barbara");
  forenames.push("Angela");
  forenames.push("Sharon");
  forenames.push("Debra");
  forenames.push("Teresa");

  // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  let surnames = [];
  surnames.push("Smith");
  surnames.push("Jones");
  surnames.push("Williams");
  surnames.push("Taylor");
  surnames.push("Brown");
  surnames.push("Davies");
  surnames.push("Evans");
  surnames.push("Wilson");
  surnames.push("Thomas");
  surnames.push("Johnson");
  surnames.push("Roberts");
  surnames.push("Robinson");
  surnames.push("Thompson");
  surnames.push("Wright");
  surnames.push("Walker");
  surnames.push("White");
  surnames.push("Edwards");
  surnames.push("Hughes");
  surnames.push("Green");
  surnames.push("Hall");
  surnames.push("Lewis");
  surnames.push("Harris");
  surnames.push("Clarke");
  surnames.push("Jackson");
  surnames.push("Wood");
  surnames.push("Turner");
  surnames.push("Martin");
  surnames.push("Cooper");
  surnames.push("Hill");
  surnames.push("Ward");

  const initalNodeName = 'Hiro protaganist';
  let groupNumber = 0;
  let lastAddedGroupName = '';
  
  // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  function RenderGraph()
  {
      // Round the corners of the nodes
      g.nodes().forEach(function(v) {
      let node = g.node(v);
      node.rx = node.ry = 5;
    });
    
    // Run the renderer. This is what draws the final graph.
    render(d3.select("svg g"), g);

    // Set up an SVG group so that we can translate the final graph.
    let svg = d3.select("svg"),
                svgGroup = svg.append("g");

    // Center the graph
    let xCenterOffset = (svg.attr("width") - g.graph().width) / 2;
    svgGroup.attr("transform", "translate(" + xCenterOffset + ", 20)");
    svg.attr("height", g.graph().height + 40);    


    let selections = inner.selectAll("g.node");
    selections
        .on('mouseover', function(d) {
          console.log('mouseover ' + d);
        })
        // .on('mouseout', function(d) {
        //   console.log('mouseout ' + d);
        // })
        // .on('mousedown', function(d) {
        //   console.log('mousedown ' + d);
        // })
        // .on('mouseup', function(d) {
        //   console.log('mouseup ' + d);
        // })
        .on('click', function (d) {
          alert('You clicked "' + d + '"'); 
        });
  }

  // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  function AddGroup()
  {
    let groupName = 'Group_' + ++groupNumber;
    lastAddedGroupName = groupName;
    console.log('Added ' + groupName);
    g.setNode(groupName, { label: groupName, clusterLabelPos: 'bottom' });
    RenderGraph();
  }

  // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  function AddPerson()
  {
    let numChildrenInGroup = 0;

    if (groupNumber === 0)  // No groups, so create one, to add the new person to
    {
      AddGroup();
    }
    else
    {
      numChildrenInGroup = g.children(lastAddedGroupName).length;

      // 1 chance in 4 to add a new group & switch to that
      if ((Math.floor(Math.random() * 4) + 1) === 1)
      {    
        AddGroup();
        numChildrenInGroup = g.children(lastAddedGroupName).length;
      }
      else
      {
        if (numChildrenInGroup == 4)
        {
          AddGroup();
          numChildrenInGroup = 0;
        }
      }
    }

    let forname = forenames[Math.floor(Math.random() * forenames.length)];
    let suname  = surnames[Math.floor(Math.random() * surnames.length)];
    let name    = forname + ' ' + suname;

    let level = 1;   // FixMe:
    if (numChildrenInGroup > 0)
    {
      level = Math.floor(Math.random() * 2) + 1;
    }

    let borderColour = 'green';
    if (level === 2)
      borderColour = 'blue';

    g.setNode(name, {label: name, style: 'stroke: ' + borderColour, level: level});
    g.setParent(name, lastAddedGroupName);
    
    g.setEdge(initalNodeName, name, { arrowhead: 'undirected', label: level} );

    console.log('Added ' + name);
    RenderGraph();
  }   // AddPerson()

  // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
  // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=

  // Create the input graph
  let g = new dagreD3.graphlib.Graph({compound:true})
    .setGraph({})
    .setDefaultEdgeLabel(function() { return {}; });

   // Here we're setting the initial, central node
  g.setNode(initalNodeName, {label: initalNodeName, style: 'stroke: red', level: 2});

  // Create the renderer
  let render = new dagreD3.render();

  // // Set up an SVG group so that we can translate the final graph.
  let svg = d3.select("svg"),
      inner = svg.append("g");

// ToDo: Look at https://github.com/dagrejs/dagre-d3/issues/144          
// // Set up zoom support
// let zoom = d3.behavior.zoom().on("zoom", function() {
//     inner.attr("transform", "translate(" + d3.event.translate + ")" +
//                                 "scale(" + d3.event.scale + ")");
//   });
// svg.call(zoom);

  RenderGraph();

</script>

</body>
</html>

and here's the CSS:

body {
  width: 960px;
  margin: 0 auto;
  color: #333;
  font-weight: 300;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
}

h1 {
  font-size: 3em;
  font-weight: 300;
}

h2 {
  font-size: 1.5em;
  font-weight: 300;
}

section {
  margin-bottom: 3em;
}

section p {
  text-align: justify;
}

svg {
  border: 1px solid #ccc;
  overflow: hidden;
  margin: 0 auto;
}

pre {
  border: 1px solid #ccc;
}
question from:https://stackoverflow.com/questions/65869171/how-can-i-auto-zoom-my-dagre-graph-after-dynamically-adding-a-node

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...