This map is very impressive. I would be happy to apply this to other use cases. However, the code is really difficult to understand. A step by step approach to this kind of cartogram would be really useful to me.
Happy to pair on this—which parts of the code do you find most difficult? (There's the initialization, the central OT algorithm, and a bit of plumbing to make it look nice in the browser.)
I would say the intialization algorithm would be first step. I find the visualization really good even in the static form but it's complex to understand what is needed to reproduce the visualization for other examples.
Could you please explain what's happening in the calculation of the coords? Are you kind of drawing a spiral of units, ordered by population size?
And what is the relevance of phi here?
This cell creates the dots and their initial positions.
It takes all the communes one by one, in density order. For each commune it divides its number of habitants by the precision, to get the number of dots to generate for this commune. Then it generates the dots at the location (centroid) of that commune, but instead of putting them all exactly in the same spot (which would confuse the layout algorithms), it dispatches them on a very small spiral around the centroid.
Since the number of dots is integer, we accumulate rounding errors in the "rest" variable, so that we don't lose inhabitants from smaller communes. If a village has 890 hab. and the precision is 500, we generate 1 dot. The 390 hab that are not taken into account by that dot will be added to the next village, which is "similar" in density—maybe it has 923 hab. That second commune will then generate 2 dots (923+390 div 500), and the new rest will be 313, passed to the next village etc. Overall it means that everyone will be accounted for, except maybe the last inhabitants from the largest city (0 to 499 persons). We could make this slightly more accurate by rounding up if the final rest is > precision/2.
When the cells of a commune get separated in two groups (which happens when a small commune is elongated by the process), this makes the points jump back together. The goal is to ensure that a commune is always made of contiguous cells.
I understand that this function accelerates the Lloyd Relaxation somehow...but I don't understand what this code is doing.
Two things that stump me:
1) What does "projection" represent? Why add the recalculated x + y coordinates of the points in:
ca * points[2 * i] + sa * points[2 * i + 1]
2) How does the chordAreaInverse function come into play?
Thanks,
Ben
As I see it, this is the main algorithm (sliced optimal transport); and Lloyd's relaxation is just coating. For an explanation, you can refer to the following notebook goes through the steps one by one https://observablehq.com/@fil/disc-transport
I want to write a better explainer with more helper functions, so please continue posting questions :)
Nice! That's great.
So each iteration of the algo picks a random angle, and then evenly distributes points across the axis of that angle?
So the first iteration arranges the points in a single line, and then successive iterations proceed to puff the points out from evenly across the disc?
That's about it, yes! Each iteration moves the points in the direction that will make their projections more “evenly distributed” along that axis. So, if the values are projected against the x axis, only the x values will be modified: the y values will stay unchanged—in other words, the points will not be crushed onto the x axis. And we do this gently and along many different axes, to avoid any criss-crossing effects. By “evenly distributed”, we mean that the profile of the distribution will match the expected profile if the points were already in the right place (this is where the inverse chord area function kicks in).
Cool - I get it now. The inverse chord function distributes the points so there are more towards the middle (where the disc is fatter), and fewer towards the ends (where the disc tapers).
Playing with the code, I also noticed that if you iterate through the axes sequentially (i.e. walk around the circle in an clockwise manner) ,the points to not distribute across the disc. It only works if there is randomisation in the order of the projected axes.