Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make d3.js force layout gravity rectangular?

In d3.js force layout, giving a gravity value makes layout circular.

enter image description here However, I'd like to make force layout rectangular, while nodes have a negative charge and even distance. (like above)

Is there any way to make force layout rectangular?

Can I achieve this by modifying tick function?

Here is my code:

var background = d3.select('.background');

var width = background.node().getBoundingClientRect().width,
	height = background.node().getBoundingClientRect().height;

var nodes = d3.range(50).map(function(d, i) {
		return {
      		text: "Hello"
		};
	});

var messages = background.selectAll('.message')
	.data(nodes)
	.enter()
		.append('div')
		.attr('class', 'message')
		.text(d => d.text)
		.each(function(d, i) {
			d.width = this.getBoundingClientRect().width;
			d.height = this.getBoundingClientRect().height;
		});

var force = d3.layout.force()
	.gravity(1/88)
	.charge(-50)
	.nodes(nodes)
	.size([width, height]);

messages.call(force.drag);

force.on('tick', function(e) {
	messages.each(d => {
		d.x = Math.max(0, Math.min(width - d.width, d.x));
		d.y = Math.max(0, Math.min(height - d.height, d.y));
	});
	
	messages.style('left', d => `${d.x}px`)
		.style('top', d => `${d.y}px`);
});

force.start();
body {
  padding: 0;
  margin: 0;
}
.background {
  width: 100%;
  height: 100vh;
  border: 1px solid #007aff;
}
.message {
  display: inline-block;
  font-family: sans-serif;
  border-radius: 100vh;
  color: white;
  padding: 10px;
  background-color: #007aff;
  position: absolute;
}
.boundary {
  display: inline-block;
  width: 10px;
  height: 10px;
  background-color: red;
  position: absolute;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<body>
  <div class="background">
  </div>
</body>
like image 729
Stleamist Avatar asked Feb 15 '26 05:02

Stleamist


1 Answers

OK, edit 3: in d3v4, forceCollide can be used to set minimum distances between the nodes, if you then use a positive strength, this draws the nodes together, helping set them an even distance apart (although it looks better for circles than rectangles):

var force = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-10))
.force("collide", d3.forceCollide(30).strength(1).iterations(1))
.force('x', d3.forceX(width/2).strength(0.5))
.force('y', d3.forceY(height/2).strength(10));

Assuming the nodes are in a rectangular svg, limiting them to within the centre of the SVG can help even out the edges:

position.nodes(nodes).on('tick', function ticks() {
        nodes.attr("cx", function(d) {
            return d.x = Math.max(20, Math.min(width + 20, d.x))
        }).attr("cy", function(d) {
            return d.y = Math.max(20, Math.min(height + 20, d.y));
        })
    });

and playing around with the force strengths can help draw them in along the y-axis:

var position = d3.forceSimulation(nodes).force("charge", d3.forceManyBody())
.force('x', d3.forceX(width/2).strength(1))
.force('y', d3.forceY(height/2).strength(5));

fiddle here.

It seems that the forces are a lot more customisable in v4 than v3, I think forceCollide integrated some workarounds into the library. So, you can try and find a v3 workaround, or maybe look into upgrading to v4.

In v3 I played around with the gravity, charge and limiting x and y to maintain the nodes in the box a bit better, fiddle here. But I don't know enough about v3 to improve it much beyond that.

like image 168
Oliver Houston Avatar answered Feb 17 '26 19:02

Oliver Houston



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!