Reputation: 256
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
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