Maybe you've seen them: Rainbows of circles representing members of the U.S. Senate, House of Representatives, or all of Congress.
I wanted to make such a visualization to show the number of members of Congress who've tested positive for coronavirus and the positive tests among each party.
If not for the serious nature of the topic, it was a fun puzzle to solve.
The steps I took were:
- Figure out how many circles fit in each ring
- Calculate the positions for every circle
- Sort the positions to suit my needs
- Marry the positions to data
It turns out that once I established a) the number of circles in each ring and b) the size of those circles, I could figure out the rest with code.
You can play with the final results, or take a look at the code yourself. But here's the explanation:
Figure out how many circles fit in each ring
I needed to chart 538 circles, and the fastest way to figure out how many circles fit in each half-ring was to Google around for examples of how others had done it before. I quickly found this example created by Ashley J. Perks:
The generational breakdown of the 117th Congress, beautifully visualized by @kerning pic.twitter.com/LyaR5BYT5R
— Reid Wilson (@PoliticsReid) November 29, 2020
I simply counted the circles in her rings and adjusted them so they added up to my total. (I got additional inspiration from Herb Susmann's Observable chart of the U.S. Senate.)
Later, after publishing the first version of my story, I built a calculator that uses trial-and-error to fit a given number of circles into a set number of rings.
Once I had an array of the circles in each ring, like [28,31,34,37,40,43,46,50,53,56,59,61]
, I was ready for the next step.
Calculating positions for every circle
I'm pretty sure this is the first time I've used cosine to solve a puzzle since the "real world" examples from high school trigonometry. The goal was to find the x
and y
coordinates for each circle in each ring.
I could solve for x
and y
with the radius of the ring and the angle of the resulting right triangle:
Getting the ring radius
The ring's radius can be gleaned from its "half circumference," which I knew: It's essentially the diameter of each circle placed next to each other with a gap in between, which I set to 1 pixel.
With this half circumference, I used the formula for the circumference of a circle (c = 2πr
) to calculate the first ring radius:
first ring radius = number of circles * (diameter of a circle + gap) / π
Getting the triangle's center angle
The angle of the entire semicircle is 180 degrees, or π radians. So to get the angle of the triangle (Θ), I divided π by one fewer than the number of circles I have (there's one fewer slices of the "pie" than there are circles), and multiplied that angle by the position of the circle on the ring — in the case of the first ring, numbers from 0 to 27.
Getting Y
Now it is easy to calculate y
relative to the center point of the rings: it's the first ring radius times the sine of the angle.
Getting X
And x
, relative to the center of the rings, is the radius times the cosine of the angle. Since that formula actually calculates x
to the right of the center point, I invert it by multiplying it by -1
, because the X axis of the entire graphic begins at the leftmost edge of the graphic (more on that in a moment).
Getting 'em all
With this, I step through all of the rings in the array, extending the radius by the diameter of a circle plus the gap each time.
As I do, I store the angle, ring number, ring radius, relative x
, and relative y
for each position in an array of positions.
Sort the positions to suit my needs
As built so far, the order of positions is the first ring clockwise, then the second ring clockwise, and so on. So highlighting the first 50 circles looks like this:
But I wanted to show my data more like a "pie slice" (or bundt cake slice).
The fix was to sort the positions array first by their angles, in ascending order, and then — since some circles fall along the same angle — by ring number, in descending order.
In the new order, the first 50 points look like this:
Marry the positions to data
Whether it's 538 members of Congress (including non-voting members) or the artificial data set I make in the online calculator, each item in the data set gets placed on the chart in order as the D3.js library I use draws the graphic. So the first item gets placed in the first position, second item in the second position, etc.
In D3, the (0,0) point of the graphic is the upper-left corner, so the actual x
position will be 1/2 the width of the whole graphic plus the relative x
of each individual circle (relative, again, to the center point of the rings).
The actual y
position is the relative y
subtracted from the height of the graphic plus the radius of one circle — otherwise half of the lowest circles fall outside the graphic.
To get the positive-test data in the middle of the Times chart, I separated the Republicans and the Democrats, sorted each set by positivity status (one ascending, the other descending) and joined them back together before drawing the chart.