AnimNations
AnimNations

Reputation: 256

Increase Distance Between Nodes in Network/Chart

I'm trying to figure out a way to space out the nodes in my network graph for my d3.js code. I don't necessarily care about how the shape of the network will be when I load the page since I can just click and drag around the nodes to make any kind of shape I want. But I'm not really sure where I start in trying to space out my nodes. I searched around and nothing I found seems to work for me. Help is very much appreciated.

Here is a picture of what the network looks like when I load the page: https://i.gyazo.com/919ad4bde39d9fe6a6b6c91548dbcc2f.png

Here is what I'd like for it to look like roughly (again, shape does not really matter, I'm just looking to get a little distance on the inital load): https://i.gyazo.com/fefa29cf861e204bc83f34cbc2d1a17d.png

(I only have 8 rep so I can't upload pictures sorry)

Here's my code so far:

<!DOCTYPE html>
<style>

    .links line {
        stroke-opacity: 0.6;
    }

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

</style>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">

<head>

    <meta charset="utf-8" />
    <title>Group Comments</title>
    <script src="http://d3js.org/d3.v4.min.js"></script>

</head>

<body>
    <p> Not Ready: Group 6 Comments </p>
    <svg width="960" height="600"></svg>
    <script>

        //fetches the svg
        var svg = d3.select("svg"),
            width = +svg.attr("width"),
            height = +svg.attr("height");

        //Sets a color scale
        var color = d3.scaleOrdinal(d3.schemeCategory20);

        var strokeColor = d3.scaleLinear()
            .domain([0, 1, 2])
            .range(["white", "red", "green"]);

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

        //reads the JSON file
        d3.json("NR6comments.json", function (error, graph) {
            if (error) throw error;

            //sets up the "links" between the nodes
            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) })
                    .attr("stroke", function (d) { return strokeColor(d.value) });

            //sets up the nodes
            var node = svg.append("g")
                .attr("class", "nodes")
                .selectAll("circle")
                .data(graph.nodes)
                .enter().append("circle")
                    .attr("r", 10)
                    .attr("fill", function (d) { return color(d.group); })
                    .call(d3.drag()
                        .on("start", dragstarted)
                        .on("drag", dragged)
                        .on("end", dragended));

            //displays the ID number on a node when hovering over
            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
                    .attr("cx", function (d) { return d.x; })
                    .attr("cy", function (d) { return 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>
    <p><a href="likes.html">Likes Chart</a></p>
</body>
</html>

I would greatly appreciate it if I could get some help with this problem. Thank you!

Upvotes: 3

Views: 1851

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102219

There are different ways to achieve what you want. The easiest one is setting the strength of your manyBody method. According to the API:

If strength is specified, sets the strength accessor to the specified number or function, re-evaluates the strength accessor for each node, and returns this force. A positive value causes nodes to attract each other, similar to gravity, while a negative value causes nodes to repel each other, similar to electrostatic charge.

Since I don't have access to your data, this is a simplified demo. The first version has no strength, just like your code:

var width = 400;
var height = 300;

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

var nodes = [{
  name: "foo",
  color: "blue"
}, {
  name: "bar",
  color: "green"
}, {
  name: "baz",
  color: "red"
}, {
  name: "foofoo",
  color: "yellow"
}, {
  name: "foobar",
  color: "blue"
}, {
  name: "foobaz",
  color: "green"
}, {
  name: "barfoo",
  color: "red"
}, {
  name: "barbar",
  color: "yellow"
}, {
  name: "barbaz",
  color: "blue"
}];

var links = [{
  "source": 0,
  "target": 1
}, {
  "source": 0,
  "target": 2
}, {
  "source": 0,
  "target": 3
}, {
  "source": 1,
  "target": 3
}, {
  "source": 1,
  "target": 4
}, {
  "source": 2,
  "target": 5
}, {
  "source": 3,
  "target": 6
}, {
  "source": 1,
  "target": 7
}, {
  "source": 6,
  "target": 8
}, {
  "source": 0,
  "target": 7
}, {
  "source": 2,
  "target": 6
}, {
  "source": 3,
  "target": 8
}];

var simulation = d3.forceSimulation()
  .force("link", d3.forceLink())
  .force("charge", d3.forceManyBody())
  .force("center", d3.forceCenter(width / 2, height / 2));

var link = svg.selectAll(null)
  .data(links)
  .enter()
  .append("line")
  .style("stroke", "#ccc")
  .style("stroke-width", 1);

var node = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("circle")
  .attr("r", function(d) {
    return d.r = 10;
  })
  .attr("stroke", "gray")
  .attr("stroke-width", "2px")
  .attr("fill", function(d) {
    return d.color
  });

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

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

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

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

The second version, however, has the strength set to a negative value:

.force("charge", d3.forceManyBody().strength(-500))

Here it is:

var width = 400;
var height = 300;

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

var nodes = [{
  name: "foo",
  color: "blue"
}, {
  name: "bar",
  color: "green"
}, {
  name: "baz",
  color: "red"
}, {
  name: "foofoo",
  color: "yellow"
}, {
  name: "foobar",
  color: "blue"
}, {
  name: "foobaz",
  color: "green"
}, {
  name: "barfoo",
  color: "red"
}, {
  name: "barbar",
  color: "yellow"
}, {
  name: "barbaz",
  color: "blue"
}];

var links = [{
  "source": 0,
  "target": 1
}, {
  "source": 0,
  "target": 2
}, {
  "source": 0,
  "target": 3
}, {
  "source": 1,
  "target": 3
}, {
  "source": 1,
  "target": 4
}, {
  "source": 2,
  "target": 5
}, {
  "source": 3,
  "target": 6
}, {
  "source": 1,
  "target": 7
}, {
  "source": 6,
  "target": 8
}, {
  "source": 0,
  "target": 7
}, {
  "source": 2,
  "target": 6
}, {
  "source": 3,
  "target": 8
}];

var simulation = d3.forceSimulation()
  .force("link", d3.forceLink())
  .force("charge", d3.forceManyBody().strength(-500))
  .force("center", d3.forceCenter(width / 2, height / 2));

var link = svg.selectAll(null)
  .data(links)
  .enter()
  .append("line")
  .style("stroke", "#ccc")
  .style("stroke-width", 1);

var node = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("circle")
  .attr("r", function(d) {
    return d.r = 10;
  })
  .attr("stroke", "gray")
  .attr("stroke-width", "2px")
  .attr("fill", function(d) {
    return d.color
  });

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

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

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

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

Upvotes: 3

Related Questions