简体   繁体   中英

How to make d3.js force layout gravity rectangular?

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

在此输入图像描述 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 => { dx = Math.max(0, Math.min(width - d.width, dx)); dy = Math.max(0, Math.min(height - d.height, dy)); }); messages.style('left', d => `${dx}px`) .style('top', d => `${dy}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> 

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM