Home > database >  Force network to fit bounding box with d3
Force network to fit bounding box with d3

Time:10-12

I'm trying to fit my network inside a div. I used this example : https://bl.ocks.org/puzzler10/2531c035e8d514f125c4d15433f79d74 but can't make it work while using d3.json()

This is the code I'm using.

<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<style>

html, body {
  height: 100%;
  margin: 0;
}

.links line {
  stroke: #999;
  stroke-opacity: 0.6;
}

.nodes circle {
  stroke: #fff;
  stroke-width: 1.5px;
}

text {
  font-family: sans-serif;
  font-size: 10px;
  color: black;
}

.sidebar {
  height: 100%;
  width: 0;
  margin-top:10%;
  margin-bottom:1.5%;
  margin-left:1.5%;
  position: absolute;
  z-index: 2000;
  top: 0;
  left: 0;
  bottom:0;
  background-color:rgba(240,240,240,0.6);
  overflow-x: hidden;
  transition: 0.5s;
  border-radius:5px;
  overflow: hidden;
}
    
.sidebar:hover {
  overflow-y: scroll;
}

svg{
  border: 1px solid black;
}

.container {
  display: flex;   
  justify-content: center
};


</style>

<div class="container">
<svg width="500" height="500"></svg>
</div>


<div id="mySidebar" class="sidebar">
  <p id="area"></p>
  <p id="desc"></p>
  <div>
    <img id="media" src="">
  </div>
  <p id="content"></p>
</div>


<script src="https://d3js.org/d3.v4.min.js"></script>
<script>


//Navbar navigation
function openNav() {
  document.getElementById("mySidebar").style.width = "350px";
}

function closeNav() {
  document.getElementById("mySidebar").style.width = "0";
}

//Graph options
var radius = 15;

var svg = d3.select("div#container")
  .append("svg")
  .attr("preserveAspectRatio", "xMinYMin meet")
  .attr("viewBox", "0 0 300 300")
  .classed("svg-content", true);

var svg = d3.select("svg"),
    width =  svg.attr("width"),
    height =  svg.attr("height");

var color = d3.scaleOrdinal(d3.schemeCategory20);

var simulation = d3.forceSimulation()
                    .force("link", d3.forceLink().id(function(d) { return d.id; }))
                    .force("charge", d3.forceManyBody().strength(-2000))
                    .force("center", d3.forceCenter(width / 2, height / 2));


//Graph creation
d3.json("http://myjson.dit.upm.es/api/bins/10fx", function(error, graph) {
  if (error) throw error;
  var link = svg.append("g")
      .attr("class", "links")
      .selectAll("line")
      .data(graph.links)
      .enter().append("line")
      .attr("stroke-width", function(d) { return Math.sqrt(d.value); });

  var node = svg.append("g")
                .attr("class", "nodes")
                .selectAll("g")
                .data(graph.nodes)
                .enter().append("g")

  var circles = node.append("circle")
    .attr("r", 5)
    .attr("fill", function(d) { return color(d.group); });

  // Create a drag handler and append it to the node object instead
  var drag_handler = d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);

  drag_handler(node);
  
  var labels = node.append("text")
      .text(function(d) {
        return d.id;
      })
      .attr('x', 6)
      .attr('y', 3);

  link.on("click", 
    function(d) {
      console.log(d.content);
      openNav();
      document.getElementById("content").innerHTML = d.content;
    },
  )

  node.append("title")
      .text(function(d) { return d.id; });

  simulation
      .nodes(graph.nodes)
      .on("tick", ticked);

  simulation.force("link")
      .links(graph.links);

  function ticked() {
    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 //This is where the "problem" seems to be located
        .attr("transform", function(d) {
          return "translate("   d.x   ","   d.y   ")";
        })

  }
});

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

</script>

The function that can constrain the network to the bounding box is the following :

function ticked() {
   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 //This is where the "problem" seems to be located
       .attr("transform", function(d) {
         return "translate("   d.x   ","   d.y   ")";
       })

 }

I've tried many things but can't make it work. Any ideas ?

CodePudding user response:

The part of the block that you shared that keeps the nodes inside the bounds is

node
    .attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
    .attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); });

Note the calls to Math.max and Math.min to make sure that d.x and d.y are kept in the bounds. Your code is missing this.

You can do something similar:

node
    .attr("transform", function(d) {
      d.x = Math.max(radius, Math.min(width - radius, d.x));
      d.y = Math.max(radius, Math.min(height - radius, d.y));
      return "translate("   d.x   ","   d.y   ")";
    });

Here's an updated example:

<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<style>

html, body {
  height: 100%;
  margin: 0;
}

.links line {
  stroke: #999;
  stroke-opacity: 0.6;
}

.nodes circle {
  stroke: #fff;
  stroke-width: 1.5px;
}

text {
  font-family: sans-serif;
  font-size: 10px;
  color: black;
}

.sidebar {
  height: 100%;
  width: 0;
  margin-top:10%;
  margin-bottom:1.5%;
  margin-left:1.5%;
  position: absolute;
  z-index: 2000;
  top: 0;
  left: 0;
  bottom:0;
  background-color:rgba(240,240,240,0.6);
  overflow-x: hidden;
  transition: 0.5s;
  border-radius:5px;
  overflow: hidden;
}
    
.sidebar:hover {
  overflow-y: scroll;
}

svg{
  border: 1px solid black;
}

.container {
  display: flex;   
  justify-content: center
};


</style>

<div class="container">
<svg width="500" height="500"></svg>
</div>


<div id="mySidebar" class="sidebar">
  <p id="area"></p>
  <p id="desc"></p>
  <div>
    <img id="media" src="">
  </div>
  <p id="content"></p>
</div>


<script src="https://d3js.org/d3.v4.min.js"></script>
<script>


//Navbar navigation
function openNav() {
  document.getElementById("mySidebar").style.width = "350px";
}

function closeNav() {
  document.getElementById("mySidebar").style.width = "0";
}

//Graph options
var radius = 15;

var svg = d3.select("div#container")
  .append("svg")
  .attr("preserveAspectRatio", "xMinYMin meet")
  .attr("viewBox", "0 0 300 300")
  .classed("svg-content", true);

var svg = d3.select("svg"),
    width =  svg.attr("width"),
    height =  svg.attr("height");

var color = d3.scaleOrdinal(d3.schemeCategory20);

var simulation = d3.forceSimulation()
                    .force("link", d3.forceLink().id(function(d) { return d.id; }))
                    .force("charge", d3.forceManyBody().strength(-2000))
                    .force("center", d3.forceCenter(width / 2, height / 2));


//Graph creation
d3.json("http://myjson.dit.upm.es/api/bins/10fx", function(error, graph) {
  if (error) throw error;
  var link = svg.append("g")
      .attr("class", "links")
      .selectAll("line")
      .data(graph.links)
      .enter().append("line")
      .attr("stroke-width", function(d) { return Math.sqrt(d.value); });

  var node = svg.append("g")
                .attr("class", "nodes")
                .selectAll("g")
                .data(graph.nodes)
                .enter().append("g")

  var circles = node.append("circle")
    .attr("r", 5)
    .attr("fill", function(d) { return color(d.group); });

  // Create a drag handler and append it to the node object instead
  var drag_handler = d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);

  drag_handler(node);
  
  var labels = node.append("text")
      .text(function(d) {
        return d.id;
      })
      .attr('x', 6)
      .attr('y', 3);

  link.on("click", 
    function(d) {
      console.log(d.content);
      openNav();
      document.getElementById("content").innerHTML = d.content;
    },
  )

  node.append("title")
      .text(function(d) { return d.id; });

  simulation
      .nodes(graph.nodes)
      .on("tick", ticked);

  simulation.force("link")
      .links(graph.links);

  function ticked() {
    node
        .attr("transform", function(d) {
          d.x = Math.max(radius, Math.min(width - radius, d.x));
          d.y = Math.max(radius, Math.min(height - radius, d.y));
          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; });
  }
});

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

</script>

  • Related