Venndicon

A Venn diagram icon — three overlapping circles with random positions, sizes, and colors for each of the eight regions.

How Venndicons Are Produced

Each venndicon is a small SVG built from three circles drawn on a square canvas. The circles overlap to create a classic Venn diagram with eight distinct regions, and every region gets its own randomly chosen color.

The Parameters

Each circle has a random center position (anywhere from 20% to 80% of the canvas on each axis) and a random radius (40% to 100% of the canvas width). With three circles that is 9 random geometric values. Then 8 random colors are chosen — one for each region of the Venn diagram. In total, 17 random values define a venndicon.

The Eight Regions

Variety

When a seed integer is provided the random number generator is deterministic, so the same seed always produces the same venndicon. Here are seeds 0 through 15:

Matching Venndicons to Images

A grid of venndicons can be arranged so that, when viewed from a distance, the grid approximates a target photograph. The process has four steps:

  1. Divide the target image into a grid. The photo is split into, say, 80×80 square cells. The grid is centered on the image and any leftover pixels at the edges are cropped.
  2. Analyze each cell's color. Every cell is subdivided into four quadrants (top-left, top-right, bottom-left, bottom-right) and the average RGB color of each quadrant is recorded. This gives a 4-color fingerprint per cell.
  3. Generate a pool of candidate venndicons. We create at least as many venndicons as there are grid cells (often more, so the algorithm can be selective). Each venndicon is rendered and its own four-quadrant color fingerprint is computed the same way. When image color sampling is on, the eight colors of each venndicon are drawn from the photograph's palette rather than being fully random, which gives the pool a much better starting distribution.
  4. Solve the assignment problem. We need a one-to-one mapping from venndicons to grid cells that minimizes total color distance. This is a classic assignment problem and is solved optimally by the Hungarian algorithm.

Color Distance

The "distance" between a venndicon and an image cell is the sum of squared Euclidean RGB distances across all four quadrants:

distance = sum(
    (rv − ri)2 + (gv − gi)2 + (bv − bi)2
    for each quadrant in [TL, TR, BL, BR]
)

Using four quadrants rather than a single average gives the matching spatial awareness — a venndicon that is dark on top and bright on bottom will be placed where the photo has the same gradient, not just a similar overall luminance.

The Hungarian Algorithm

The assignment problem is: given n workers and m jobs with a cost for every worker–job pair, find a one-to-one assignment of workers to jobs that minimizes total cost. In our case the "workers" are venndicons and the "jobs" are image cells.

History

The algorithm was published by Harold Kuhn in 1955 and named "the Hungarian method" because it was largely based on earlier work by two Hungarian mathematicians, Dénes König and Jenő Egerváry. James Munkres refined it in 1957, which is why it is also known as the Kuhn–Munkres algorithm or the Munkres assignment algorithm.

Problem Setup

Build an n × m cost matrix C where entry C[i][j] is the color distance between venndicon i and image cell j. For an 80×80 grid that is 6,400 cells, so the matrix has 6,400 columns. If we generate 6,400 venndicons the matrix is square; if we generate more (say 7,000) then the algorithm naturally picks the best 6,400 from the pool.

Cell 0Cell 1Cell 2Cell 3
V 0412031089005430
V 1520678032009100
V 2760043002100890
V 3310055006704200

Highlighted entries show the optimal assignment that minimizes total cost.

How It Works

The algorithm proceeds iteratively, maintaining a system of dual variables (potentials) for rows and columns. At each step it tries to extend a maximum matching among the "tight" edges (those where the cost equals the sum of the row and column potentials). The core loop is:

  1. Initialize potentials. Set each row potential u[i] to the minimum cost in that row. Set each column potential v[j] to zero (or to the column minimum of the reduced matrix).
  2. Find tight edges and build a matching. An edge (i, j) is "tight" when C[i][j] − u[i] − v[j] = 0. Among tight edges, find a maximum matching using augmenting paths.
  3. Check for optimality. If every row is matched the current assignment is optimal — stop. Otherwise, some rows remain unmatched and we need to create new tight edges.
  4. Adjust potentials. Find the minimum slack δ among edges between unmatched rows and unmatched columns. Add δ to the potentials of matched columns in the alternating tree and subtract δ from unmatched rows. This creates at least one new tight edge without destroying existing ones.
  5. Repeat from step 2 until a perfect matching is found.

Complexity

The algorithm runs in O(n3) time in the worst case, where n = max(workers, jobs). For our 80×80 grid (6,400 cells) this is on the order of 2.6×1011 operations in the very worst case, though in practice scipy.optimize.linear_sum_assignment uses an optimized C implementation (the Jonker–Volgenant algorithm, also known as LAPJV) that finishes in seconds.

Why Not Greedy?

A greedy approach — assign the lowest-cost pair first, then the next, and so on — is fast but produces suboptimal results. Early assignments can "steal" good venndicons from cells that have no other good match. The Hungarian algorithm guarantees the global optimum: no rearrangement of the same venndicons can reduce total color distance.

Multiple Images

When matching the same pool of venndicons against multiple photographs (as in the gallery above), a shared pool is generated once, and the Hungarian algorithm runs independently for each image. This means every image gets its own optimal arrangement of the same set of venndicons. The viewer above lets you hover over a cell in one image and see where that same venndicon ended up in the other — they travel to very different positions because each image has its own optimal layout.