Cartogram studio

The same cartogram, solved in your browser instead of in R. Change a parameter and it re-solves: states are rescaled to their seat count, dropped into their slots, pushed apart until nothing overlaps, carved into equal-area cells, and the cells matched back to districts. How each step works →

Geography U.S. states

GeoJSON or TopoJSON, any polygon layer — or just drop a file on the map. Longitude/latitude is detected and projected equal-area; already-projected files are used as they are.

Drives everything downstream — each region's size and its number of cells.

Two columns: a region name or code, and a number. A header is optional. Matched by code or name, so CA,52 and California,52 both work.

The area divisor. Lower makes every state bigger, so the map fills more of the frame — and collides sooner.

The gap the relaxation enforces between any two states. Zero skips relaxation and leaves them in their slots.

Placement hand-drawn slots

Both apply only when the hand-drawn slots cannot hold the seat counts. Compaction is gravity toward the centre of mass: it packs the map tighter but does not care who borders whom. Neighbour pull springs each pair of real neighbours toward the spacing they had, so the map keeps its shape — more faithful, less compact. Both are annealed as the solve settles.

More points means a better area estimate and a slower solve. The R build uses 600.

The sampling is the only randomness. Same seed, same cells.

placement
carving
matching
arrangement
vs. the R build

Your own seats, and your own map

Seats — pick Custom and type a number for any region. The total need not be 435: each region is sized by its share of whatever the total comes to, so 78 seats across nine regions works exactly as well as 435 across fifty.

Geography — load any GeoJSON or TopoJSON polygon layer. Longitude/latitude is detected and projected with an Albers equal-area conic fitted to your data; anything outside ±180 by ±90 is assumed to be already projected and is only fitted to the frame. Equal-area is not a preference here but a requirement: the cartogram scales every region so that drawn area tracks seat count, which is only meaningful if the source areas tracked ground area, so a Mercator file would quietly inflate the north. Seat counts are read from a seats property if there is one; region names from name, NAME, st, GEOID and similar.

Three things only exist for the built-in map and switch off for a file: colouring by the 2022 winner, the hand-drawn slots and their nudges, and the comparison against the R build. An uploaded region is seeded at its own centre instead of in a hand-drawn slot — which is what those slots are an artist's refinement of. Region scales vary by an order of magnitude on a real map, so seeding at centroids alone leaves the dense corners hopelessly overlapped; the arrangement is spread until the relaxation can separate it, then fitted back to the frame. The padding is enforced before that refit, so the drawn gaps come out smaller — the page reports both numbers. A topology with several layers is decoded from whichever has the most polygons, and the page says which one it used.

What is actually running here

Projection, simplification and dissolve are mapshaper's, done once — those are the topological operations, and they are baked into the state outlines this page loads. Everything numerical is redone live by web/solver.js: the seat-proportional rescaling, the boundary-disc relaxation, uniform sampling, k-means, the capacity-balanced power diagram, and the Hungarian assignment. It is a port of the R code, and it is checked against it — sh/12_test_solver.js confirms the assignment finds the same optimum R does and that the relaxation leaves no two states closer than the padding.

Two honest caveats. First, cells here are the raw convex polygons; the browser clips them to the state outline at paint time with an SVG clipPath rather than intersecting the geometry as mapshaper does. Identical on screen, but not true clipped geometry — exact areas and exported files come from the R build. Second, this will not land on pixel-identical positions to that build. The relaxation is a Gauss–Seidel iteration, which is chaotic: a difference of 10⁻¹³ in the first pass grows by roughly a factor of ten per iteration. Every run is a valid re-solve, not a reproduction. What holds either way is the invariant — no two states closer than the padding you asked for.

Updated · solved in your browser by web/solver.js