ps-aux
ps-aux

Reputation: 12174

Simple graph layout in d3.js?

I would like to draw a simple graph with d3.js like this one

enter image description here

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

Answers (1)

Ian
Ian

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:

  1. Define a set of nodes - basically an array of things to represent the circles
  2. Append the svg:circle elements to the DOM using .data(nodes)
  3. Defined a set of links
  4. Append the 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

Related Questions