function updateNodePositions()

in code/decision-tree/js/EntropyBubble.js [184:270]


    function updateNodePositions() {
      // tally entropy
      let posArr = Array.from({ length: self.counters["positive"] }).map(
        () => "positive"
      );
      let negArr = Array.from({ length: self.counters["negative"] }).map(
        () => "negative"
      );
      let entropyArr = posArr.concat(negArr);
      let currentProbability = prob(entropyArr);

      // update bounding circle based on length of entropyArr
      const n = entropyArr.length;

      // update center labels values
      selectAll("text.center-label").text((d, i) => {
        if (i === 0) {
          return `Entropy: ${format(".3f")(entropy(entropyArr))}`;
        } else if (i === 1) {
          return `# Positive Class:  ${self.counters["positive"]}`;
        } else {
          return `# Negative Class:  ${self.counters["negative"]}`;
        }
      });

      self.positions.center.x = self.xScale(currentProbability);
      self.updatedYPos = self.yScale(entropy(entropyArr));
      select("#bounding-circle")
        .style("stroke-width", () => (entropyArr.length > 0 ? 1.4 : 0))
        .transition()
        .attr("cx", self.xScale(currentProbability))
        .attr("cy", self.updatedYPos)
        .attr("r", (d) => self.boundingCircleScale(entropyArr.length));

      // re-run force simulation
      self.simulation
        .force(
          "x",
          forceX((d, i) => {
            if (d.group === "center") {
              return self.positions.center.x;
            }
            return d.value === "positive"
              ? self.positions.positive.x
              : self.positions.negative.x;
          }).strength(0.055)
        )
        .force(
          "y",
          forceY((d, i) => {
            if (d.group !== "center") {
              return self.chartHeight;
            }
            return self.updatedYPos;
          }).strength(0.055)
        )

        .force("bound-inner-dots", () => {
          self.node
            .filter((d) => d.group === "center")
            .each((d) => {
              // once node is inside the bounding circle (plus some padding to account for collide radius)
              // assign data to mark, so we can then bound it in the following force
              if (
                distance([d.x, d.y], [self.WIDTH / 2, self.updatedYPos]) <
                self.bc_radius + self.boundingOffset
              ) {
                d.isBounded = true;
                const theta = Math.atan(
                  (node.y - self.updatedYPos) / (node.x - self.WIDTH / 2)
                );
                node.x =
                  self.WIDTH / 2 +
                  self.bc_radius *
                    Math.cos(theta) *
                    (node.x < self.WIDTH / 2 ? -1 : 1);
                node.y =
                  self.updatedYPos +
                  self.bc_radius *
                    Math.sin(theta) *
                    (node.x < self.WIDTH / 2 ? -1 : 1);
              }
            });
        })
        .on("tick", ticked);
      self.simulation.alpha(1).restart();
    }