TransWikia.com

Draw circle packing with Tikz?

TeX - LaTeX Asked by user226320 on January 30, 2021

Is it possible to draw the attached picture?enter image description here

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}

2 Answers

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)));

enter image description here

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}

Answered by g.kov on January 30, 2021

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.

enter image description here

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}

enter image description here

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}

enter image description here

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 path=circ1] (c1) circle[radius=ra];
            path[name path=circ2] (c2) circle[radius=rb];
            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)$),
                    n0= {ra}, % Radius
                    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}

Answered by Sandy G on January 30, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP