Reputation: 12174
I would like to draw a simple graph with d3.js like this one
I failed to find out the graph layout in both in v5 API documentation and on the Internet.
All examples use d3.force
layout from d3-force. Although it looks impressive, I am not looking for a graph layout with physicall simulation.
Is there a simpler layout or is the best way to use d3.force
layout with switched off physics ?
Upvotes: 1
Views: 4439
Reputation: 34549
There isn't any need to use the d3.forceSimulation
, that ultimately is just one of the many D3 layouts that are on offer. If you look at Mike Bostocks example Force Directed Graph then the modifications you need are as follows:
nodes
- basically an array of things to represent the circlessvg:circle
elements to the DOM using .data(nodes)
links
svg:line
elements to the DOM using .data(links)
This is essentially using D3
for it's pure DOM data binding, and not using a pre-built layout.
You can copy any of the force example really - all it's doing underneath is setting an x
and a y
position underneath. If you've already got those, just position your circles at those points.
All the parts to do with simulation
and the ticked
event you can remove from the example.
Here is an example providing a static layout:
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
const layout = (graph) => {
return new Promise((resolve) => {
var simulation = d3
.forceSimulation()
.alphaMin(0.3)
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
.on("end", resolve(graph));
simulation.nodes(graph.nodes);
simulation.force("link").links(graph.links);
});
};
const render = (graph) => {
var link = d3.select("#target").append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); })
.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; });
var node = d3.select("#target").append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("fill", function(d) { return color(d.group); });
}
d3.json("https://gist.githubusercontent.com/mbostock/4062045/raw/5916d145c8c048a6e3086915a6be464467391c62/miserables.json", function(error, graph) {
if (error) throw error;
layout(graph)
.then((graph) => render(graph));
});
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="600">
<g id="target" transform="translate(480, 300)">
</g>
</svg>
EDIT
To change the distance between the nodes you need to change some of the forces in play. The docs are really good - specifically what you want to change is https://github.com/d3/d3-force#many-body
So modify the charge
force to read:
.force("charge", d3.forceManyBody().strength(-charge))
You'll need to define the charge, and tweak it a little until the force gives you a good layout.
Upvotes: 2