class

ContributorMural::Renderers::Pebble

Inherits ContributorMural::Renderer < Reference < Object

A pile of stones: everyone is a disc sized by their rank, poured into a slab and shaken until nothing overlaps. The only style that does not fill its rectangle — the page shows between the pebbles, which means it themes itself for free the way voronoi's lead does.

The pack is a relaxation rather than a formula. Discs start on a jittered lattice across the slab, and then every sweep pushes overlapping pairs apart, carries the whole pile a step toward the slab it is aimed at, and holds it inside. width caps how wide the slab may be and density says how much of it the pebbles cover; between them they decide how tall the pile comes out.

Constants

CLIP_ID = "pebble-clip"

One clip for the whole document: the image box is exactly the disc's bounding square, so the unit circle is the pebble.

EASE = 1.08

How much the slab grows, and how many times, when the pack cannot settle inside the one it was given.

A density is a request, and whether it can be met depends on the crowd: the same figure that packs cleanly at forty people jams at eight, where there are too few discs for the edges not to dominate. Rather than pick the one figure that survives every crowd — which would be well below what looks good for the crowds people actually have — the pack loosens and tries again. Six goes at eight percent give the slab half again the room, enough to carry the tightest density the config allows down to where it settles, so "no two pebbles overlap" holds for every config rather than for the ones that happened to get tested.

EASE_TRIES = 6
FIT_RATE = 0.08

How much of the way toward the slab each sweep carries the pack. Small enough that the separation keeps up with the stretch, large enough to arrive well inside the sweep budget.

MIN_STEPS = 60
PAD = 2.0

Breathing room around the finished pile, so a pebble on the edge is not flush against the document bounds.

RELAX_BUDGET = 800000

Disc-sweeps a section may spend before the sweep count gives way. Reached at about three thousand people; below that the step count is fixed.

RELAX_STEPS = 260

Sweeps of separate-fit-hold, then a tail that only separates and holds. The tail is what makes "no two pebbles overlap" true rather than nearly true: the fit runs after the separation in every sweep, so the last thing to happen to the pack would otherwise be a squeeze that can push a pair back together. It leaves as soon as a sweep finds nothing worth moving, so its cap is a backstop rather than a cost.

RESOLVED = 1e-3

An overlap smaller than this is left alone, and a sweep that found nothing bigger counts as having settled. Without a floor the pack never stops: every sweep finds a pair a few millionths of a pixel inside its clearance, pushes it out, and nudges two more in — so the settle runs to its cap every time and the whole style comes out orders of magnitude slower than the rest. Coordinates are written to two decimals, so this is three decimals below anything a reader could see.

SALT_JITTER = 104729_u64

Voronoi's salt multiplier, in the same role: keeps one section's jitter from repeating in the next.

SETTLE_STEPS = 1000
TARGET_ASPECT = 2.4

How wide the pile wants to be against how tall, before width caps it. A mural sits above or below a README's prose, so it wants to be a band; much past this and a small crowd reads as a scattered line rather than a pile.

TOUCHING = 1e-9

Two centres this close have no direction to be pushed along.

Instance methods

fetch_size(user : ResolvedUser) : Int32

The image box is exactly the drawn diameter and the clip is inscribed in it, so unlike voronoi — where a heavy cell runs wider than its nominal pitch — nothing here outgrows the size it was fetched for. Plain 2x for high-DPI is exact.

Source
prepare(users : Array(ResolvedUser)) : Nil

Dense rank over the distinct weights, as voronoi does, so a contributor with 1000x the commits lands at the top of the taper exactly like one with 2x. Where voronoi leaves the table empty for an all-equal crowd, this fills it at full size: a rank of zero would read as "smallest pebble", and no ranking information should mean a uniform field, not a field of minimums.

Source