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();
}