I want to create a simulation of magnets floating on oil without any friction.
The magnets have different force based on their radius. They move freely until they find an ideal balance of forces. I would guess that this balance of forces will always cause the magnets to be symmetrically arranged.
I have tried to create such a simulation with d3, but the result is not always Symetrical. Even when I drag and drop the elements, they do not always move to the same position.
Is the symmetry theory fundamentally wrong? Shouldn't the elements always move to the same position? Or are the forces constructed incorrectly?
// Source: https://bl.ocks.org/HarryStevens/f636199a46fc4b210fbca3b1dc4ef372
var radius = 160;
var positives = [27, 50, 20, 20];
var negatives = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10];
/* Änderungen der Konfiguration nur oberhalb dieser Zeile */
var width = 400,
height = 400;
var myNodes = [];
for (let i = 0; i < positives.length; i++) {
myNodes.push({
charge: -positives[i]
});
}
for (let i = 0; i < negatives.length; i++) {
myNodes.push({
charge: negatives[i]
});
}
var nodePadding = 10;
var svg = d3.select("svg").attr("width", width).attr("height", height);
var simulation = d3
.forceSimulation()
.nodes(myNodes)
.force(
"forceX",
d3
.forceX()
.strength(0.1)
.x(width * 0.5)
)
.force(
"forceY",
d3
.forceY()
.strength(0.1)
.y(height * 0.5)
)
.force(
"center",
d3
.forceCenter()
.x(width * 0.5)
.y(height * 0.5)
)
.force(
"charge",
d3.forceManyBody().strength(function (d) {
return -d.charge * 10;
})
)
.force(
"radial",
d3.forceRadial(
function (d) {
return d.charge > 0 ? radius : 0;
},
width / 2,
height / 2
)
)
.force(
"collide",
d3.forceCollide().radius(function (d) {
return Math.abs(d.charge) + nodePadding;
})
)
.on("tick", function (d) {
node
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
});
svg
.append("circle")
.classed("radius", true)
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("r", radius)
.style("fill", "none")
.style("stroke", "#bbb")
.style("stroke-dasharray", 4);
var node = svg
.selectAll(".node")
.data(myNodes)
.join("circle")
.classed("node", true)
.attr("r", function (d) {
return Math.abs(d.charge);
})
.attr("fill", function (d) {
return d.charge > 0 ? "#0000ff" : "#ff0000";
})
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
d3.selectAll("circle").call(
d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended)
);
distance = ([x1, y1], [x2, y2]) =>
Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.03).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (event.active) simulation.alphaTarget(0.03);
d.fx = null;
d.fy = null;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div id="content">
<svg width="400" height="400">
</svg>
</div>
See here for a codepen: https://codepen.io/ValeSauer/pen/bGKJawe
Generally, force directed placement of nodes tends to placement that will minimize the energy inherent in the system. While, it works pretty well and is fun to watch, it does have some disadvantages, as explained in this section of Wikipedia's article on graph drawing. In particular, the process is designed to find a local minimum of the energy function, rather than a global minimum. I too would expect the global minimum to have some sort of symmetry, if the number and sizes of the nodes allows it, but the local minimum need not.
As an example to see this in action, simply reduce the number of negative nodes in your simulation to two. You should see an image that looks like so:
Now, you have a lot of forces at work here that makes this a bit tricky to analyze in depth. Among those forces, though, is a charge force that's pulling the red and blue nodes together along with a collision force at length that's pushing them apart. Thus, it's not too hard to imagine that the two negatively charged blue nodes reside in local energy wells, though it's likely that there's a more symmetrical global configuration of smaller energy.