Home > other >  Why are the results of d3.simulate not always symmetrical?
Why are the results of d3.simulate not always symmetrical?

Time:12-13

I want to create a simulation of magnets floating on oil without any friction.

enter image description here

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: enter image description here

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.

  • Related