tikz: Distribute evenly and randomly circles

Here's a macro that implements what you described. It takes four arguments: The width and height of the rectangle to be filled, the radius of the circles, and the number of attempts.

For each attempt, a random position is generated. The distances between this position and all existing circles are calculated, and if there's a collision, no circle is drawn. If the circle does not collide, it is drawn, and its coordinates are added to the list of existing circles.

This approach is far from efficient, but it works:

\fillrandomly{3}{2}{0.2}{50}

\pgfmathsetseed{2}
\fillrandomly{5}{5}{0.5}{300}

\documentclass{article}
\usepackage{tikz}

\begin{document}
\def\xlist{4}
\def\ylist{4}

\newcommand{\fillrandomly}[4]{
    \pgfmathsetmacro\diameter{#3*2}
    \draw (0,0) rectangle (#1,#2);
    \foreach \i in {1,...,#4}{
        \pgfmathsetmacro\x{rnd*#1}
        \pgfmathsetmacro\y{rnd*#2}
        \xdef\collision{0}
        \foreach \element [count=\i] in \xlist{
            \pgfmathtruncatemacro\j{\i-1}
            \pgfmathsetmacro\checkdistance{ sqrt( ({\xlist}[\j]-(\x))^2 + ({\ylist}[\j]-(\y))^2 ) }
            \ifdim\checkdistance pt<\diameter pt
                \xdef\collision{1}
                \breakforeach
            \fi
        }
        \ifnum\collision=0
            \xdef\xlist{\xlist,\x}
            \xdef\ylist{\ylist,\y}
            \draw [red, thick] (\x,\y) circle [radius=#3];
        \fi 

    }
}

\begin{tikzpicture}
\pgfmathsetseed{2}
\fillrandomly{5}{5}{0.5}{300}

\end{tikzpicture}
\end{document}

Here's a different variation. The idea is to generate a bunch of circles, and then to cull those that intersect. For the culling phase, I used a halfway implementation of Sweep & Prune which speeds things up a bit as far as collision detection goes for large n.

  • Generate points randomly
  • Sort their x-coordinates
  • Save pairs whose x-coordinates overlap for further testing, discard the rest
  • For saved pairs, determine whether y-coordinates overlap as well
  • If so, then they potentially collide, so do the actual collision test on them
  • If they collide, then discard one of the pair

The macro is \circles{n}{rad}{width}{height} and \circles{500}{.1}{10}{5} gives:

enter image description here

\documentclass{article}
\usepackage{luacode}
\usepackage{tikz}
\usetikzlibrary{backgrounds}

\begin{luacode*}

local rand = math.random
local abs = math.abs
local pts = {}
local tstpairs = {}

-- generate the points
local function genpts(n,x,y)
    for i = 1,n do
        pts[i]={}
        pts[i][1] = rand()*x
        pts[i][2] = rand()*y
    end
end

-- for sorted pairs, check if x-coords overlap
-- if so, store the pair in table tstpairs
local function getpairs(t,r)
    for i = 1,#t do
        tstpairs[i] = {}
        for j = 1,#t-i do
            if t[i+j][1]-t[i][1]<2*r then
                tstpairs[i][#tstpairs[i]+1]=i+j
            else
                break
            end
        end
    end
end

-- this is the actual collision test
-- it's less expensive to use x^2+y^2<d^2 than sqrt(x^2+y^2)<d
local function testcol(a,b,r)
    local x = pts[b][1]-pts[a][1]
    local y = pts[b][2]-pts[a][2]
    if x*x+y*y<4*r*r then
        return true
    end
end

-- this is a bit of a mess, deleting pairs on the fly was causing some
-- problems, so I had to include some checks "if pts[k1]..." etc.
local function delpairs(r)
    for k1,v1 in pairs(tstpairs) do
        if pts[k1] then
            for k2,v2 in pairs(v1) do
                if pts[v2] then
                    if abs(pts[v2][2]-pts[k1][2])<2*r then
                        if testcol(k1,v2,r) then
                            pts[v2]=nil
                        end
                    end
                end
            end
        end
    end
end

-- quickSort helper
local function partition(array, p, r)
    local x = array[r][1]
    local i = p - 1
    for j = p, r - 1 do
        if array[j][1] <= x then
            i = i + 1
            local temp = array[i][1]
            array[i][1] = array[j][1]
            array[j][1] = temp
        end
    end
    local temp = array[i + 1][1]
    array[i + 1][1] = array[r][1]
    array[r][1] = temp
    return i + 1
end

-- quickSort to sort by x
-- taken from https://github.com/akosma/CodeaSort/blob/master/QuickSort.lua
local function quickSort(array, p, r)
    p = p or 1
    r = r or #array
    if p < r then
        q = partition(array, p, r)
        quickSort(array, p, q - 1)
        quickSort(array, q + 1, r)
    end
end

-- draw output
local function showout(n,r,x,y)
    tex.print("\\begin{tikzpicture}[radius="..r.."cm]")
    tex.print("\\draw[red](0,0) rectangle ("..x..","..y..");")
    for k,v in pairs(pts) do
        tex.print("\\draw ("..pts[k][1]..","..pts[k][2]..") circle ;")
    end
    tex.print("\\end{tikzpicture}")
end

-- wrapper
function circles(n,r,x,y)
    genpts(n,x,y)
    quickSort(pts)
    getpairs(pts,r)
    delpairs(r)
    showout(n,r,x,y)
end


\end{luacode*}

\def\circles#1#2#3#4{\directlua{circles(#1,#2,#3,#4)}}

\begin{document}

\circles{500}{.1}{10}{5}

\end{document}

Just discovered this old question, and for completeness I decided to add the following answer.

Poisson disc sampling seems to be the right way to create this kind of patterns. This question/answer provides an implementation of this algorithm in pure lualatex, allowing its use from pgf/tikz.

Provided that you have poisson.sty and poisson.lua (which you can get from the mentioned answer), and a working install of lualatex, the solution will be:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{backgrounds}
\usepackage{poisson}

\begin{document}
\edef\mylist{\poissonpointslist{5}{5}{1}{20}}  
% 5 x 5 is the size of the area to fill
% 1 is the minimum distance between centers
\begin{tikzpicture}[framed,gridded,radius=0.5cm]
    \draw[red](0,0) rectangle (5,5);
    \foreach \x/\y in \mylist \draw (\x,\y) circle;
\end{tikzpicture}
\end{document}

And the result (compiled with lualatex) is:

Result