geom_image_repel() draws images like geom_image(), but moves images
that overlap each other apart before drawing them. The displacement is
computed offline (no network, no rendering, no ggrepel dependency) from
the bounding boxes of the images, so the result is deterministic: the same
plot always produces the same layout.
The algorithm works on the centres of the images, after they have been
nudged (nudge_x/nudge_y) and transformed by the coordinate system
(this is the space in which width/height are expressed, i.e. fractions
of the panel):
each image gets an axis aligned box of size
width + box.padding by height + box.padding;
for every pair of boxes that overlap on both axes, the two
images are pushed apart along the allowed direction, by
force * overlap / 2 each, away from each other;
step 2 is repeated at most max.iter times, stopping early once no
pair overlaps. The residual overlap after max.iter iterations is
about (1 - force)^max.iter of the initial one, so the default
(force = 0.1, max.iter = 100) leaves essentially no overlap.
which keeps the result reproducible. For large inputs, an adaptive uniform-grid
broad phase estimates candidate density before each sweep: sparse layouts use
one immutable grid, while dense layouts use the full scan when grid lookup and
candidate handling would cost more than checking every pair. If a sparse-grid
sweep moves any image, its candidate set is no longer current; the remainder
of that sweep conservatively falls back to the full scan. The grid costs
O(n + k) per sweep for k candidate checks (with O(n^2) worst-case dense
behaviour), while small inputs retain the full pair scan. The cleanup is
hard-capped at 256 sweeps so dense inputs cannot make cleanup grow with n
beyond that fixed number of sweeps.
Images are pushed away from each other only, they are not kept inside
the panel, and images are never re-ordered or removed. Images with
size = Inf (e.g. the background image added by geom_bgimage()) and
images that cannot be loaded are ignored by the repulsion: they keep their
original position and do not push the other images around.
The width of an image that is sized through size/by (instead of an
explicit width) is derived by grid from its height and from the
physical aspect of the panel. geom_image_repel() uses the aspect
reported by the coordinate system (coord_fixed(), exact) and assumes a
square panel otherwise, which over-estimates the width of a panel that is
wider than it is high. Pass explicit width/height when the exact box
matters.