用 TikZ 绘制随机几何图形(连接紧密的随机节点)

用 TikZ 绘制随机几何图形(连接紧密的随机节点)

我想在 TikZ 中生成一个随机几何图形。将一定数量的节点随机地放在一个正方形中,如果它们的距离最多为某个固定数 r,则每对节点都是连接的。我能够生成随机节点,但在连接它们时遇到了麻烦。

以下是生成节点的代码:

\begin{tikzpicture}
\foreach \x in {1,...,300}{
    % generate random position
    \pgfmathrandominteger{\a}{-490}{490}
    \pgfmathrandominteger{\b}{-490}{490}
    % draw and label
    \node (\x) at (\a*0.01,\b*0.01) {};
    \draw[fill] (\x) circle (1pt);      
};
\end{tikzpicture}

得出以下结论:

在此处输入图片描述

现在,假设我希望距离 <0.1 的每对都通过一个线段连接起来。该怎么做?我尝试使用一些“if”语句,但语法不正确。

答案1

使用 300 个坐标,这将需要很长时间才能编译,但理论上,您可以执行这样的操作(我只使用了 50 个坐标来说明这个想法):

\documentclass[border=10pt]{standalone}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}
\foreach \x in {1,...,50}{
    % generate random position
    \pgfmathrandominteger{\a}{-490}{490}
    \pgfmathrandominteger{\b}{-490}{490}
    % draw and label
    \coordinate (n\x) at (\a*0.01,\b*0.01);
    \draw[fill] (n\x) circle (1pt);      
};

\foreach \a in {1,...,50}{
    \path (n\a);
    \pgfgetlastxy{\ax}{\ay}
    \foreach \b in {\a,...,50}{
        \ifnum\a=\b\else
            \path (n\b);
            \pgfgetlastxy{\bx}{\by}
            \pgfmathtruncatemacro{\dist}{veclen((\bx - \ax),(\by - \ay))}
            \ifdim\dist pt<50pt
                \draw[green] (n\a) -- (n\b);
            \fi
        \fi
    }
}

\end{tikzpicture}

\end{document}

在此处输入图片描述


感谢 Qrrbrbirlbel 的评论,我修改了原始代码,使其编译速度更快,但仍需要一些时间来编译(Overleaf 编译 300 个节点大约花费 55 秒)。

  1. 将第二个循环包含在第一个\foreach循环中。测试到此点为止存在的所有节点的距离就足够了。循环越少,代码编译速度就越快。
  2. 在计算 之前veclen,先测试两个节点之间的水平和垂直距离是否已经太大。由于 的计算veclen需要一些时间,这将加快代码速度。

我还添加了两个宏来简化节点数和要绘制的线的最大距离的调整。

\documentclass[border=10pt]{standalone}
\usepackage{tikz}

\begin{document}

\newcounter{nodecount}
\setcounter{nodecount}{300}

\newlength\maxdistance
\setlength\maxdistance{20pt}

\begin{tikzpicture}
\foreach \i in {1,...,\value{nodecount}}{
    % generate random position
    \pgfmathrandominteger{\a}{-490}{490}
    \pgfmathrandominteger{\b}{-490}{490}
    % draw and label
    \coordinate (n\i) at (\a*0.01,\b*0.01);
    \draw[fill] (n\i) circle (1pt);      

    % check distances to all other existing nodes
    \foreach \j in {1,...,\i}{
        % do not draw line if nodes are identical
        \ifnum\i=\j\else                                 
            \path (n\i);
            \pgfgetlastxy{\ix}{\iy}
            \path (n\j);
            \pgfgetlastxy{\jx}{\jy}
            \pgfmathtruncatemacro{\distx}{\ix - \jx}
            \pgfmathtruncatemacro{\disty}{\iy - \jy}
            % only draw line if x distance of both nodes is not too large
            \ifdim\distx pt<\maxdistance
                % only draw line if y distance of both nodes is not too large
                \ifdim\disty pt<\maxdistance             
                    \pgfmathtruncatemacro{\distxy}{veclen((\distx),(\disty))}
                    % only draw line if distance of both nodes is not too large
                    \ifdim\distxy pt<\maxdistance 
                        \draw[green] (n\i) -- (n\j);
                    \fi
                \fi
            \fi
        \fi
    }
}

\end{tikzpicture}

\end{document}

在此处输入图片描述


由于 TeX 不适合进行如此复杂和重复的计算,我建议你使用其他更擅长此类计算的编程语言。例如,你可以使用 LuaLaTeX 编译以下代码(编译大约需要 2 秒):

\documentclass[border=10pt]{standalone}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}

\directlua{

n = 300

d = 1

x = {}
y = {}

for i = 1,n do

    a = math.random(-490,490)
    b = math.random(-490,490)
    
    x[i] = a*0.01
    y[i] = b*0.01

    tex.print('\\coordinate(' .. i .. ') at (' .. x[i] ..',' .. y[i] .. ');') 
    tex.print('\\draw[fill] (' .. i .. ') circle (1pt);') 

    for j = 1,i do
        t = math.sqrt((x[i] - x[j])^2 + (y[i] - y[j])^2)
        if t > 0 and t < d then
            tex.print('\\draw[green] (' .. i .. ') -- (' .. j .. ');') 
        end
    end
    
end

}

\end{tikzpicture}

\end{document}

答案2

我为您提供了三种解决方案:

  1. 使用 PGF 和 TeX 宏来测试长度
  2. 使用 TikZ 的calc库。
  3. 同时使用calcmath库。

第一个方法快了大约 3.5 倍。解决方案 2 和 3 花费的时间大致相同。(当然,数学运算始终相同。)

第一个函数还会检查是否确实需要进行计算,veclen因为如果X方向或direction 已经超出了所需长度(通过test lengthvalue 键指定),因此无需进行(对于 TeX)更复杂的计算。
它还使用\pgfmathveclen@而不是\pgfmathveclen因为参数已经评估,然后可以veclen直接处理。

代码 1 (PGF)

\documentclass[tikz]{standalone}
\tikzset{
  test length/.initial=5mm,
  test previous/.code 2 args={%
      \pgfpointdiff{\pgfpointanchor{dot-#1}{center}}
                   {\pgfpointanchor{dot-#2}{center}}%
      \pgfgetlastxy{\lastX}{\lastY}%
      \ifdim\lastX>\pgfkeysvalueof{/tikz/test length}\relax\else
        \ifdim\lastY>\pgfkeysvalueof{/tikz/test length}\relax\else
          \csname pgfmathveclen@\endcsname{\lastX}{\lastY}%
          \ifdim\pgfmathresult pt<\pgfkeysvalueof{/tikz/test length}\relax
            \tikzset{insert path=edge(dot-#2)}%
          \fi
        \fi
      \fi}}
\begin{document}
\pgfmathsetseed{652524}
\begin{tikzpicture}
\foreach \x in {1,...,300}{
    \pgfmathrandominteger{\a}{-490}{490}
    \pgfmathrandominteger{\b}{-490}{490}
    \node[circle, fill, inner sep=+0pt] (dot-\x) at (\a*.01, \b*.01) {}
      foreach \y in {1,...,\x} { [test previous = {\x}{\y}] };}
\end{tikzpicture}
\end{document}

代碼 2 ( calc)

\documentclass[tikz]{standalone}
\usetikzlibrary{calc}
\makeatletter
\pgfkeys{/utils/if/.code n args={3}{%
  \pgfmathparse{#1}\ifdim\pgfmathresult pt=0pt\relax
    \expandafter\pgfutil@firstoftwo\else\expandafter\pgfutil@secondoftwo\fi
    {\pgfkeysalso{#3}}{\pgfkeysalso{#2}}}}
\makeatother
\tikzset{
  test length/.initial=5mm,
  test previous/.style 2 args={%
    insert path={let \p{diff} = ($(dot-#1)-(dot-#2)$) in},
    /utils/if = {veclen(\p{diff}) < \pgfkeysvalueof{/tikz/test length}}
                {insert path={(dot-#1)edge(dot-#2)}}}}
\begin{document}
\pgfmathsetseed{652524}
\begin{tikzpicture}
\foreach \x in {1,...,100}{
    \pgfmathrandominteger{\a}{-490}{490}
    \pgfmathrandominteger{\b}{-490}{490}
    \node[circle, fill, inner sep=+0pt] (dot-\x) at (\a*.01, \b*.01) {}
      foreach \y in {1,...,\x} { [test previous/.expanded = {\x}{\y}] };}
\end{tikzpicture}
\end{document}

代碼 3 ( \tikzmath)

\documentclass[tikz]{standalone}
\usetikzlibrary{calc,math}
\tikzset{test length/.initial=5mm}
\begin{document}
\pgfmathsetseed{652524}
\begin{tikzpicture}
\foreach \x in {1,...,300}{
  \pgfmathrandominteger{\a}{-490}{490}
  \pgfmathrandominteger{\b}{-490}{490}
  \node[circle, fill, inner sep=+0pt] (dot-\x) at (\a*.01, \b*.01) {};
  \tikzmath{
    coordinate \diff; int \y;
    for \y in {1,...,\x} {
      \diff = (dot-\x)-(dot-\y);
      if veclen(\diffx, \diffy) < \pgfkeysvalueof{/tikz/test length} then {
        {\draw (dot-\x)--(dot-\y);};
      };
    };
  }
}
\end{tikzpicture}
\end{document}

输出

在此处输入图片描述

相关内容