in src/state/layout/hierarchicalLayout.ts [315:416]
function applyGravity(directions: NodeDirection[], gravityAngle: number, gravityStrength: number): void {
// No nodes or no gravity, nothing to do here.
if (!directions.length || gravityStrength <= 0) {
return;
}
// If it's one node and it's free, move it to `gravityAngle`.
// If it's fixed, nothing to do.
if (directions.length === 1) {
if (!directions[0].isFixed) {
directions[0].angle = gravityAngle;
}
return;
}
const incIndex = (i: number) => ((i + 1) % directions.length);
const decIndex = (i: number) => (i === 0 ? (directions.length - 1) : (i - 1));
// Find two nodes exactly before and after `gravityAngle`.
let nextIndex = 0;
while (nextIndex < directions.length - 1 && directions[nextIndex].angle < gravityAngle) {
nextIndex += 1;
}
if (directions[nextIndex].angle < gravityAngle) {
nextIndex = 0;
}
const previousIndex = decIndex(nextIndex);
const anglePrevious = directions[previousIndex].angle;
const angleNext = directions[nextIndex].angle;
// Push `previousIndex` and `nextIndex` towards `gravityAngle` with a "special care":
// Consider which of them are fixed and which are free and push the closest right to `gravityAngle`,
// while also ensuring that we don't press towards the neighbor too hard.
if (directions[previousIndex].isFixed && directions[nextIndex].isFixed) {
// Both sides are fixed, nothing to do.
} else if (directions[previousIndex].isFixed) {
// If `previousIndex` is fixed, move the free `nextIndex` towards gravity.
const pushedAngle = pushTowardsAngle(angleNext, anglePrevious, gravityStrength);
if (angleBetween(gravityAngle, anglePrevious) >= angleBetween(pushedAngle, anglePrevious)) {
// If moving node directly to `gravityAngle` won't squeeze it too hard to neighbor, let's do it.
directions[nextIndex].angle = gravityAngle;
} else {
// However if it gets too close, use regular `pushTowardsAngle` formula instead.
directions[nextIndex].angle = pushedAngle;
}
} else if (directions[nextIndex].isFixed) {
// If `nextIndex` is fixed, move the free `previousIndex` towards gravity.
const pushedAngle = pushTowardsAngle(anglePrevious, angleNext, gravityStrength);
if (angleBetween(gravityAngle, angleNext) >= angleBetween(pushedAngle, angleNext)) {
// If moving node directly to `gravityAngle` won't squeeze it too hard to neighbor, let's do it.
directions[previousIndex].angle = gravityAngle;
} else {
// However if it gets too close, use regular `pushTowardsAngle` formula instead.
directions[previousIndex].angle = pushedAngle;
}
} else {
// Both `previousIndex` and `nextIndex` are free.
const anglePreviousToGravity = angleBetween(anglePrevious, gravityAngle);
const angleNextToGravity = angleBetween(angleNext, gravityAngle);
if (Math.abs(angleNextToGravity - anglePreviousToGravity) < 0.01) {
// Two free nodes are equally close to gravity. Move them equally closer, based on `gravityStrength`.
directions[previousIndex].angle =
pushTowardsAngle(directions[previousIndex].angle, gravityAngle, gravityStrength);
directions[nextIndex].angle =
pushTowardsAngle(directions[nextIndex].angle, gravityAngle, gravityStrength);
} else if (anglePreviousToGravity <= angleNextToGravity) {
// `previousIndex` is closer to `gravityAngle`. Move it right to `gravityAngle`.
directions[previousIndex].angle = gravityAngle;
directions[nextIndex].angle = pushTowardsAngle(angleNext, gravityAngle, gravityStrength);
} else {
// `nextIndex` is closer to `gravityAngle`. Move it right to `gravityAngle`.
directions[nextIndex].angle = gravityAngle;
directions[previousIndex].angle = pushTowardsAngle(anglePrevious, gravityAngle, gravityStrength);
}
}
// Going counter-clockwise until we reach the diamterically opposite angle to `gravityAngle`.
let index = incIndex(nextIndex);
while (index !== previousIndex && isLeftTurn(gravityAngle, directions[index].angle)) {
if (!directions[index].isFixed) {
directions[index].angle =
pushTowardsAngle(directions[index].angle, directions[decIndex(index)].angle, gravityStrength);
}
index = incIndex(index);
}
// Going clockwise until we reach the diamterically opposite angle to `gravityAngle`.
index = decIndex(previousIndex);
while (index !== nextIndex && !isLeftTurn(gravityAngle, directions[index].angle)) {
if (!directions[index].isFixed) {
directions[index].angle =
pushTowardsAngle(directions[index].angle, directions[incIndex(index)].angle, gravityStrength);
}
index = decIndex(index);
}
sortNodeDirections(directions);
}