我想在 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 秒)。
- 将第二个循环包含在第一个
\foreach
循环中。测试到此点为止存在的所有节点的距离就足够了。循环越少,代码编译速度就越快。 - 在计算 之前
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
我为您提供了三种解决方案:
- 使用 PGF 和 TeX 宏来测试长度
- 使用 TikZ 的
calc
库。 - 同时使用
calc
和math
库。
第一个方法快了大约 3.5 倍。解决方案 2 和 3 花费的时间大致相同。(当然,数学运算始终相同。)
第一个函数还会检查是否确实需要进行计算,veclen
因为如果X方向或是direction 已经超出了所需长度(通过test length
value 键指定),因此无需进行(对于 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}