Draw circle packing with Tikz?

Is it possible to draw the attached picture?

My tried:

\documentclass[tikz,border=2mm]{standalone}
\usetikzlibrary{fit,backgrounds}
\begin{document}
\begin{tikzpicture}[cable/.style={circle, fill=cyan!70!black, minimum size=5mm, inner sep=0pt, outer sep=0pt}]
\node[cable] (center) at (0,0) {};
\foreach \i in {0,1,...,6}
\node[cable] (1-\i) at (60*\i:5mm) {};
\fill[red!20] circle (0.26);
\end{tikzpicture}
\end{document}

• Can you describe your attached image? (what is it about in detailed?...) Oct 9, 2020 at 2:50
• @BlackMild when squeeze five circle, the shape of the middle one start to facet. Think about packing six spheres in small area; at some point they squeeze each other and their surface start to deform. Oct 9, 2020 at 11:21

Here is an automated solution. We define a command \circlesqueeze that takes two arguments, one optional. The required argument is a list of circles separated by commas. The command

\circlesqueeze{1/-1/.5,0/-1/.85,.3/.5/.7,1.3/1.2/.6,1/0/.5}


inside a tikzpicture will squeeze 5 circles. The syntax for each circle is x/y/r where (x,y) is the center and r is the radius. So in the above example, the first circle is centered at (1,-1) with radius .5. The second is centered at (0,-1) with radius .85 and so on. Units are centimeters.

The optional argument is the minimum space between circles. Default is .05cm. Here is an example with the space reduced to .03cm.

\circlesqueeze[.03]{0/0/1,1/1/.5,0/1.5/.7,1.6/1.4/.25,1.6/0/.8,2/1/.5,1/2/.5}


If you want to change the line width, it has to be done globally using the command \lw, initially set to .4. You can change this with \renewcommand. You can use the \clip command to get a picture similar to your original. I approximated the centers and radii:

\renewcommand{\lw}{1}
\begin{tikzpicture}[line width=\lw]
\clip (-4.5,-4.5) rectangle (6,5);
\circlesqueeze[.1]{.2/0/3,2/5/3,-2.6/3/2.1,-3.7/-2.3/3.1,1.5/-4/2.2,6.4/0/4}
\end{tikzpicture}


Here is the basic algorithm:

• All the circles are drawn. We want to delete appropriate circular arcs and replace them with line segments.
• For each pair of circles, the distance between their centers is compared with the sum of their radii. If the distance creates a sufficiently large gap between the circles then do nothing.
• If the circles are close (or overlapping) then calculate a point (m) on the line connecting the two centers (the center axis), between the two circular arcs that will need to be modified, but closer to the center of the smaller circle so that the two flattened segments are the same length.
• Find intersection points of each of the two circles with lines perpendicular to the center axis, equally spaced from (m).
• Cover the circular arc between these intersection points with a thick white arc to "erase" it. Note: if the background color is not white, this needs to be changed.
• In order for the flat segment to connect nicely to the remaining circle, a small (distance=\eps) segment is drawn from a second intersection slightly farther from (m), then following the first pair of intersections, and reconnecting to the circle. This is a doubled line (white on the outside, black on the inside) to cover up any undesirable remnants of the original circle.

Known issues:

• A circle cannot contain the center of a different circle.
• No point can be in the interior of more than two circles.
• Bad things can happen if there isn't enough circular arc between flat parts. This can often be fixed by adjusting the line thickness and spacing.
• Bad things can happen if the optional argument is too small or too big. It depends on the spacing of the circles.
• It's slow.

Here is the code:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{intersections,calc}
\usepackage{ifthen}

\newcommand{\eps}{.01} % arc-line connector length in cm
\newcommand{\lw}{.4} % line width. Be careful if defining locally.

\newcommand{\circlesqueeze}[2][.05]{
\foreach \xa/\ya/\ra in {#2}
{
\draw (\xa,\ya) circle[radius=\ra]; % draw all circles
}
\foreach [var=\xa, var=\ya, var=\ra, count=\na] in {#2}
\foreach [var=\xb, var=\yb, var=\rb, count=\nb] in {#2}
{
\ifthenelse{\na<\nb} % compare each pair of circles once
{
\pgfmathparse{#1+\ra+\rb-veclen(\xb-\xa,\yb-\ya)}
\ifthenelse{\lengthtest{\pgfmathresult pt > 0 pt}} % if circles are too close
{
\coordinate(c1) at (\xa,\ya); \coordinate(c2) at (\xb,\yb); % circle centers
\path[name path=line0] (c1)--(c2); % center axis
\path[name intersections={of=line0 and circ1, by={i1}},
name intersections={of=line0 and circ2, by={i2}}];
\coordinate (u12) at ($(c1)!1cm!(c2)-(c1)$); % unit vector from c1 to c2
\path
let \n1={max(\ra,\rb)}, \n2={min(\ra,\rb)},
\n3={(.5*\n2/\n1)*(\ra>=\rb)+(1-.5*\n2/\n1)*(\ra<\rb)} in % n3=.5 would be equally spaced. Otherwise, closer to smaller circle
coordinate (m) at ($\n3*(i2)+{(1-\n3)}*(i1)$);
\path[name path=insideline1] % perpendicular to center axis
let \p0=(u12),
\p1=(m)
in (\x1-\ra*\y0-#1*.5*\x0,\y1+\ra*\x0-#1*.5*\y0)--(\x1+\ra*\y0-#1*.5*\x0,\y1-\ra*\x0-#1*.5*\y0);
\path[name path=insideline2]
let \p0=(u12),
\p1=(m)
in (\x1-\ra*\y0+#1*.5*\x0,\y1+\ra*\x0+#1*.5*\y0)--(\x1+\ra*\y0+#1*.5*\x0,\y1-\ra*\x0+#1*.5*\y0);
\path[name path=outsideline1] % a little farther apart
let \p0=(u12),
\p1=(m)
in (\x1-\ra*\y0-#1*.5*\x0-\eps*\x0,\y1+\ra*\x0-#1*.5*\y0-\eps*\y0)--(\x1+\ra*\y0-#1*.5*\x0-\eps*\x0,\y1-\ra*\x0-#1*.5*\y0-\eps*\y0);
\path[name path=outsideline2]
let \p0=(u12),
\p1=(m)
in (\x1-\ra*\y0+#1*.5*\x0+\eps*\x0,\y1+\ra*\x0+#1*.5*\y0+\eps*\y0)--(\x1+\ra*\y0+#1*.5*\x0+\eps*\x0,\y1-\ra*\x0+#1*.5*\y0+\eps*\y0);
\path[name intersections={of=circ1 and insideline1, by={a1,a2}}];
\path[name intersections={of=circ1 and outsideline1, by={b1,b2}}];
\path[name intersections={of=circ2 and insideline2, by={a3,a4}}];
\path[name intersections={of=circ2 and outsideline2, by={b3,b4}}];
\draw[white, line width=2*\lw] % first circle
let \p1=($(a1)-(c1)$),
\p2=($(a2)-(c1)$),
\n1 = {atan2(\y1,\x1)}, % angle 1
\n2 = {atan2(\y2,\x2)}, % angle 2
\n3 = {\n2+360*(\n1-\n2>180)-360*(\n1-\n2<-180)} % force shorter arc
in (a1) arc(\n1:\n3:\n0);
\draw[white, line width=2*\lw] % second circle
let \p1=($(a3)-(c2)$),
\p2=($(a4)-(c2)$),
\n0= {\rb},
\n1 = {atan2(\y1,\x1)},
\n2 = {atan2(\y2,\x2)},
\n3 = {\n2+360*(\n1-\n2>180)-360*(\n1-\n2<-180)}
in (a3) arc(\n1:\n3:\n0);
\draw[line join=round, white, double=black, double distance=\lw] (b1)--(a1)--(a2)--(b2);
\draw[line join=round, white, double=black, double distance=\lw] (b3)--(a3)--(a4)--(b4);
}{} % if dist >= #1 do nothing
}{}% if \na >= \nb do nothing (only do each pair of circles once)
}
}

\begin{document}

\begin{tikzpicture}[line width=\lw]
\circlesqueeze{1/-1/.5,0/-1/.85,.3/.5/.7,1.3/1.2/.6,1/0/.5}
\end{tikzpicture}

\begin{tikzpicture}[line width=\lw]
\circlesqueeze[.03]{0/0/1,1/1/.5,0/1.5/.7,1.6/1.4/.25,1.6/0/.8,2/1/.5,1/2/.5}
\end{tikzpicture}

\renewcommand{\lw}{1}
\begin{tikzpicture}[line width=\lw]
\clip (-4.5,-4.5) rectangle (6,5);
\circlesqueeze[.1]{.2/0/3,2/5/3,-2.6/3/2.1,-3.7/-2.3/3.1,1.5/-4/2.2,6.4/0/4}
\end{tikzpicture}

\end{document}

• +1 Very nice job ! Oct 30, 2020 at 7:29
• This answer is a thesis material
– Diaa
Nov 4, 2020 at 21:19
• @Diaa, if you mean I should have spent more time on my thesis and less time writing LaTeX code, you're probably right! Nov 4, 2020 at 21:24
• It can go this way too :)
– Diaa
Nov 4, 2020 at 22:02

Using Asymptote, you can start with something like this:

// circpacking.asy
//
// run
//   asy circpacking.asy
//
// to get a standalone circpacking.pdf
//
settings.outformat="pdf";
size(6cm);
pen linePen=darkblue+0.7bp;

pair o=(0.47,0);
real r0=0.2;
pair[] Ok={( 0.53, 0.48),(-0.32, 0.20),( 0.31,-0.55),( 0.78,-0.24),( 0.82, 0.12),};
real[] rk={0.28,0.62,0.37,0.19,0.17,};

int n=Ok.length;
real sq=0.08;

guide g0=circle(o,r0);
guide[] gk; gk.cyclic=true;
transform tr;
for(int i=0;i<n;++i){
tr=shift(sq*(o-Ok[i]));
Ok[i]=tr*Ok[i];
gk.push(circle(Ok[i],rk[i]));
}

guide trunc(int k){
pair[] xp;
guide q;
xp.append(intersectionpoints(gk[k],gk[k-1]));
xp.append(intersectionpoints(gk[k],g0));
xp.append(intersectionpoints(gk[k],gk[k+1]));
q=xp[0]--xp[1]
&arc(Ok[k],xp[1],xp[4])--xp[5]
&arc(Ok[k],xp[5],xp[2])--xp[3]
&arc(Ok[k],xp[3],xp[0])&cycle;
tr=shift(sq*0.3*(Ok[k]-o));
q=tr*q;
return q;
}

guide trunc0(){
pair[] xp; guide q;
for(int i=0;i<n;++i){
xp.append(intersectionpoints(g0,gk[i]));
}
q=xp[0]--xp[1]
&arc(o,xp[1],xp[2])--xp[3]
&arc(o,xp[3],xp[4])--xp[5]
&arc(o,xp[5],xp[6])--xp[7]
&arc(o,xp[7],xp[9])--xp[8]
&arc(o,xp[8],xp[0])
&cycle;
return q;
}
for(int i=0;i<n;++i){
draw(trunc(i),linePen);
}
draw(trunc0(),linePen);
clip(box(o-2*r0*(1,1),o+2*r0*(1,1)));


The idea is, starting from a Steiner chain of kissing circles, move all the circles in a chain toward the surrounded circle, find all the intersections, cut straight the overlaps and slightly move all the truncated circles backwards in order to make some gaps.

Edit

And this is a translation of the above asymptote code via .svg format to TikZ by means of svg2tikz:

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\begin{document}
\definecolor{c00003f}{RGB}{0,0,63}
\def \globalscale {1.000000}
\begin{tikzpicture}[y=0.80pt, x=0.80pt, yscale=-\globalscale, xscale=\globalscale, inner sep=0pt, outer sep=0pt]
\begin{scope}[cm={{0.99626,0.0,0.0,0.99626,(41.5276,138.898)}}]
\path[draw=c00003f,line cap=round,line join=round,line width=0.562pt,miter limit=10.04] (121.8530,43.4044) -- (145.4980,24.3523) .. controls (152.8360,14.4281) and (157.1750,2.1513) .. (157.1750,-11.1389) .. controls (157.1750,-44.1405) and (130.4220,-70.8936) .. (97.4204,-70.8936) .. controls (70.8777,-70.8936) and (48.3770,-53.5876) .. (40.5863,-29.6438) -- (62.6986,37.4991) .. controls (65.5025,39.5043) and (68.4870,41.2732) .. (71.6231,42.7764) -- (109.1410,47.4744) .. controls (113.5770,46.5924) and (117.8350,45.2196) .. (121.8530,43.4044) -- cycle;
\end{scope}
\begin{scope}[cm={{0.99626,0.0,0.0,0.99626,(41.5276,138.898)}}]
\path[draw=c00003f,line cap=round,line join=round,line width=0.562pt,miter limit=10.04] (58.6962,38.8146) -- (36.5965,-28.3310) .. controls (12.8622,-63.8082) and (-27.5709,-87.1681) .. (-73.4600,-87.1681) .. controls (-146.5380,-87.1681) and (-205.7790,-27.9268) .. (-205.7790,45.1511) .. controls (-205.7790,118.2290) and (-146.5380,177.4700) .. (-73.4600,177.4700) .. controls (-56.3476,177.4700) and (-39.9938,174.2220) .. (-24.9818,168.3080) -- (39.4559,114.1780) .. controls (41.2222,111.2950) and (42.8812,108.3390) .. (44.4273,105.3150) -- (58.8082,48.3489) .. controls (58.8334,47.2860) and (58.8461,46.2200) .. (58.8461,45.1511) .. controls (58.8461,43.0269) and (58.7960,40.9143) .. (58.6962,38.8146) -- cycle;
\end{scope}
\begin{scope}[cm={{0.99626,0.0,0.0,0.99626,(41.5276,138.898)}}]
\path[draw=c00003f,line cap=round,line join=round,line width=0.562pt,miter limit=10.04] (42.4082,117.7020) -- (-22.0387,171.8220) .. controls (-24.4716,179.4210) and (-25.7851,187.5210) .. (-25.7851,195.9280) .. controls (-25.7851,239.5460) and (9.5744,274.9060) .. (53.1925,274.9060) .. controls (96.8107,274.9060) and (132.1700,239.5460) .. (132.1700,195.9280) .. controls (132.1700,187.5340) and (130.8610,179.4470) .. (128.4350,171.8580) -- (104.9390,136.2890) .. controls (101.9690,133.7100) and (98.8053,131.3500) .. (95.4719,129.2320) -- (53.3114,116.9720) .. controls (53.2718,116.9720) and (53.2322,116.9720) .. (53.1925,116.9720) .. controls (49.5343,116.9720) and (45.9342,117.2200) .. (42.4082,117.7020) -- cycle;
\end{scope}
\begin{scope}[cm={{0.99626,0.0,0.0,0.99626,(41.5276,138.898)}}]
\path[draw=c00003f,line cap=round,line join=round,line width=0.562pt,miter limit=10.04] (107.1520,134.8290) -- (130.6250,170.4120) .. controls (135.8100,172.8190) and (141.5880,174.1620) .. (147.6790,174.1620) .. controls (170.0770,174.1620) and (188.2340,156.0050) .. (188.2340,133.6070) .. controls (188.2340,117.9240) and (179.3330,104.3210) .. (166.3070,97.5734) -- (137.4190,94.3609) .. controls (134.0410,95.2416) and (130.8340,96.5470) .. (127.8590,98.2167) -- (108.4510,123.2800) .. controls (107.5850,126.5770) and (107.1240,130.0380) .. (107.1240,133.6070) .. controls (107.1240,134.0160) and (107.1300,134.4230) .. (107.1520,134.8290) -- cycle;
\end{scope}
\begin{scope}[cm={{0.99626,0.0,0.0,0.99626,(41.5276,138.898)}}]
\path[draw=c00003f,line cap=round,line join=round,line width=0.562pt,miter limit=10.04] (137.6100,92.6742) -- (166.4940,95.8951) .. controls (181.2740,91.3063) and (192.0080,77.5236) .. (192.0080,61.2340) .. controls (192.0080,41.1933) and (175.7620,24.9470) .. (155.7210,24.9470) .. controls (152.6640,24.9470) and (149.6950,25.3251) .. (146.8590,26.0369) -- (123.2120,45.0971) .. controls (121.3490,48.8433) and (120.1140,52.9571) .. (119.6460,57.3006) -- (129.6540,86.4646) .. controls (131.9970,88.8845) and (134.6740,90.9791) .. (137.6100,92.6742) -- cycle;
\end{scope}
\begin{scope}[cm={{0.99626,0.0,0.0,0.99626,(41.5276,138.898)}}]
\path[draw=c00003f,line cap=round,line join=round,line width=0.562pt,miter limit=10.04] (108.8560,49.7248) -- (71.3386,45.0229) .. controls (68.2199,46.1067) and (65.2658,47.5420) .. (62.5235,49.2814) -- (48.1261,106.2530) .. controls (49.7824,109.1980) and (51.7779,111.9260) .. (54.0597,114.3850) -- (96.2278,126.6520) .. controls (100.0570,125.6470) and (103.6760,124.1230) .. (107.0050,122.1600) -- (126.4140,97.0975) .. controls (127.3320,93.8808) and (127.8820,90.5090) .. (128.0160,87.0304) -- (118.0060,57.8663) .. controls (115.3660,54.7351) and (112.2850,51.9889) .. (108.8560,49.7248) -- cycle;
\end{scope}
\end{tikzpicture}
\end{document}

• Thanks, @g.kov Is this tikz? Oct 9, 2020 at 17:54
• @user226320: No, it's an alternative, Asymptote, it's part of the standard TeXLive distribution. Oct 9, 2020 at 18:15
• Thanks so much!. Oct 9, 2020 at 18:26
• @user226320: ... but, if you really need this in TikZ, you can use a translation of the asy code via .svg format to TikZ by means of svg2tikz, see the updated answer. Oct 9, 2020 at 18:57