How Much Z Space for Packed Spheres?

Share
Sets of nested transparent spheres representing the proportion of strains present in the microbiom of two subjects.
A diptych displaying two subjects from the study.

I'm currently working on a new piece with Andrew Demirjian tracing the development of infant gut microbiomes. Given the hierarchical nature of the bacterial taxonomy in our source data, I'm using d3 pack to generate a circle-packed layout for the most dominant bacterial strains.

However, because I'm rendering the visualization in 3d, with class groupings orbiting their phylum, and those phyla orbiting their containing subject, that left open a question; how much space do we have to play with in the z dimension?

Without thinking too much about it I just dropped in the parent's radius as the z value, but of course that quickly resulted in orbits that went way outside the containing parent's sphere, making the 3D layout incoherent. Putting pen to paper and breaking the problem down a bit I realized I needed to account for the current sphere's x and y offset from it's parent's center.

A notebook page where I was working through the problem.

So what I actually need to figure out is; how much space there is in the z dimension from the current sphere's position to the edge of it's containing parent. To keep things simple I decided to consider the x and y dimensions separately and then use the smaller of the two results.

Now I just need to find the length of one leg of a right triangle. I've got the length of the hypotenuse for free; that's the radius of the parent group, and I've got another leg which is the x or y position of the current sphere. How do I find the last one, the distance in the z dimension to the edge of the containing parent sphere? Luckily my math-teaching partner was around to tell me that there's in fact a pretty well-known way to do this called the Pythagorean theorem.

Here's the function I ended up with and how it's applied to each datum:

function pythagThirdLeg(a: number, c: number) {
  return Math.sqrt(Math.abs(Math.pow(c, 2) - Math.pow(a, 2)));
}

const xPythag = pythagThirdLeg(datum.x, parent.r);
const yPythag = pythagThirdLeg(datum.y, parent.r);
datum.z = Math.max(0, Math.min(xPythag, yPythag) - datum.r);
0:00
/0:13