在不使用绝对位置并“手动”完成所有操作的情况下创建类似于下图(自由群的凯莱图)的 TikZ 图片的最佳方法是什么?
该图片具有非常递归的结构,因此我认为使用自动生成可能是一种更有效的方法。我说得对吗?
答案1
下面是使用该库的示例。它需要该库的lindenmayersystems
最新版本。PGF
arrows.meta
\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{lindenmayersystems,arrows.meta}
\newcount\quadrant
\pgfdeclarelindenmayersystem{cayley}{
\rule{A -> B [ R [A] [+A] [-A] ]}
\symbol{R}{ \pgflsystemstep=0.5\pgflsystemstep }
\symbol{-}{
\pgfmathsetcount\quadrant{Mod(\quadrant+1,4)}
\tikzset{rotate=90}
}
\symbol{+}{
\pgfmathsetcount\quadrant{Mod(\quadrant-1,4)}
\tikzset{rotate=-90}
}
\symbol{B}{
\draw [dot-cayley] (0,0) -- (\pgflsystemstep,0)
node [font=\footnotesize, midway,
anchor={270-mod(\the\quadrant,2)*90}, inner sep=.5ex]
{\ifcase\quadrant$a$\or$b$\or$a^{-1}$\or$b^{-1}$\fi};
\tikzset{xshift=\pgflsystemstep}
}
}
\tikzset{
dot/.tip={Circle[sep=-1.5pt,length=3pt]}, cayley/.tip={Stealth[]dot[]}
}
\begin{document}
\begin{tikzpicture}
\draw l-system [l-system={cayley, axiom=[A] [+A] [-A] [++A], step=5cm, order=4}];
\end{tikzpicture}
\end{document}
如果不需要标签(或箭头),那么它可以更简单:
\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{lindenmayersystems}
\pgfdeclarelindenmayersystem{cayley}{
\rule{F -> F [ R [F] [+F] [-F] ]}
\symbol{R}{
\pgflsystemstep=0.5\pgflsystemstep
}
}
\begin{document}
\begin{tikzpicture}
\draw l-system [l-system={cayley, axiom=[F] [+F] [-F] [++F], step=5cm, order=6}];
\end{tikzpicture}
\end{document}
并使用:
\draw l-system [l-system={cayley, axiom=[F] [+F] [-F] [++F] [--F],
angle=72, step=5cm, order=6}];
结果是
和
\draw l-system [l-system={cayley, axiom=[F] [+F] [-F], angle=120,step=5cm, order=6}];
给出
最后,根据评论中的要求,这里有一个版本,它以度数开始90
,在第一次迭代后切换到自定义角度。在这种情况下,标签的逻辑不清楚,所以线条用它们的角度标记(为此需要一点混乱):
\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{lindenmayersystems,arrows.meta,calc}
\def\tikzpoint{\csname tikz@scan@one@point\endcsname\pgfpointtransformed}
\pgfdeclarelindenmayersystem{cayley}{
\rule{A -> B [ R [A] [+A] [-A] ]}
\symbol{R}{ \pgflsystemstep=0.5\pgflsystemstep }
\symbol{>}{\tikzset{rotate=90}}
\symbol{B}{
\pgfmathanglebetweenpoints{\tikzpoint(0,0)}{\tikzpoint(\pgflsystemstep,0)}
\pgfmathparse{int(round(\pgfmathresult))}%
\let\lineangle=\pgfmathresult
\draw [dot-cayley] (0,0) -- (\pgflsystemstep,0)
node [pos=2/3, transform shape, font=\tiny, above] {$\lineangle$};
\tikzset{xshift=\pgflsystemstep}
}
}
\tikzset{
dot/.tip={Circle[sep=-1.5pt,length=3pt]}, cayley/.tip={Stealth[]dot[]}
}
\begin{document}
\begin{tikzpicture}
\draw l-system [l-system={cayley, axiom=[A] [>A] [>>A] [>>>A], step=5cm, angle=60, order=4}];
\end{tikzpicture}
\end{document}
答案2
这是一种使用的可能性tikzmath
,但可能不是“最佳方式”。
\documentclass[border=7mm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc,math}
\tikzmath{
% function that draws the edge (\x1,\y1) -- (\x2,\y2)
% with label depending on: \l=0 => "a", \l=1 => "b"
% \c is a count downd counter, we stop at \c=0
function drawgen(\x1,\y1,\x2,\y2,\c,\l)
coordinate \p;
\p1 = (\x1 pt,\y1 pt);\p2 = (\x2 pt,\y2 pt);
if (\l < .5) then {let \g=a;let \a=above;} else {let \g=b;let \a=right;};
if (\x1 > \x2 || \y1 > \y2 ) then {let \e=^{-1};} else {let \e={};};
{
\draw[draw=red,-latex] (\px1,\py1) -- node[\a,pos=.55]{$\g\e$} (\px2,\py2);
};
if (\c > 0) then {
\c = \c-1;
\p3 = 1.5*(\px2,\py2)-.5*(\px1,\py1);
drawgen(\px2,\py2,\px3,\py3,\c,\l);
\p3 = (\px2,\py2)+.5*(\py2,\px2)-.5*(\py1,\px1);
drawgen(\px2,\py2,\px3,\py3,\c,1-\l);
\p3 = (\px2,\py2)-.5*(\py2,\px2)+.5*(\py1,\px1);
drawgen(\px2,\py2,\px3,\py3,\c,1-\l);
};
};
}
\begin{document}
\begin{tikzpicture}
\tikzmath{
\d=5cm; \n=3;
drawgen(0,0,\d,0,\n,0);
drawgen(0,0,-\d,0,\n,0);
drawgen(0,0,0,\d,\n,1);
drawgen(0,0,0,-\d,\n,1);
}
\end{tikzpicture}
\end{document}
更新 1:在我的第一个版本中,我忘记了$^{-1}$
。所以我添加了以下行:
if (\x1 > \x2 || \y1 > \y2 ) then {let \e=^{-1};} else {let \e={};};
-1
将边缘向下和向右的功率设置为。当出现一些重叠时,我调整了边缘标签的位置。
更新2:由于每个人都采用自己的未标记方法,因此这里采用math
tikz 库的方法。
\documentclass[border=7mm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc,math}
\begin{document}
\begin{tikzpicture}[scale=.02pt]
\tikzmath{
\power=2; \deviation=90; \numsteps=7; let \startcolor=green; let \endcolor=red;
function branch(\x,\y,\rotate,\step){ \step=\step-1;
if (\step >= 0) then { \mix = int(100*\step/(\numsteps-1));
{\draw[shift={(\x pt,\y pt)},scale=\power^\step, rotate=\rotate, color=\startcolor!\mix!\endcolor]
(0,0)--(1,0) coordinate(newbase);};
coordinate \b; \b1 = (newbase);
for \a in {-\deviation,0,\deviation}{
branch(\bx1,\by1,mod(\rotate+\a,360),\step);
};
};
};
for \angle in {0,90,180,-90}{
branch(0,0,\angle,\numsteps);
};
}
\end{tikzpicture}
\end{document}
使用以下参数:
\power=2.3; \deviation=70; \numsteps=7; let \startcolor=magenta; let \endcolor=black;
使用以下参数:
\power=2.5; \deviation=120; \numsteps=7; let \startcolor=black; let \endcolor=orange;
答案3
第二个提议
如果不需要箭头和标签,这里有一个非常简单的递归解决方案,使用单路径,在订单 8 处(如果您尝试订单 9,pdflatex
则会停止并出现错误TeX capacity exceeded...
:)。
\documentclass[tikz]{standalone}
\usetikzlibrary{calc}
\newcommand\caley[3]{% level, length, angle
\ifnum0<#1 \pgfextra{
\pgfmathtruncatemacro\newlev{#1-1}
\pgfmathsetmacro\len{#2}
\pgfmathtruncatemacro\angle{#3}
} -- ++(\angle:\len pt)
\foreach \a in {-90,0,90}{\caley{\newlev}{\len/2}{\angle+\a}} ++(\angle:-1*\len pt)
\fi%
}
\begin{document}
\tikz \draw[red] (0,0) \foreach \a in {-90,0,90,180}{\caley{8}{4cm}{\a}};
\end{document}
第一个命题
这是一个使用递归的解决方案旋转宏:
\documentclass[tikz]{standalone}
\usepackage{ifthen}
\usetikzlibrary{calc}
\tikzset{
my label/.style={font=\scriptsize,inner sep=2pt},
a/.style={my label,above,node contents={$a$}},
b/.style={my label,right,node contents={$b$}},
a-1/.style={my label,above,node contents={$a^{-1}$}},
b-1/.style={my label,right,node contents={$b^{-1}$}},
}
\newcommand\caley[6]{% level, length, l1, l2, l3, l4
\ifthenelse{0<#1}{
\pgfmathtruncatemacro\newlev{#1-1}
\pgfmathtruncatemacro\len{#2}
\draw[draw=red,-latex] (0,0) -- (\len pt,0) node[pos=.6,#3] coordinate (O);
\begin{scope}[shift={(O)}]
\begin{scope}[rotate=90] \caley{\newlev}{\len/2}{#4}{#5}{#6}{#3} \end{scope}
\begin{scope}[rotate=0] \caley{\newlev}{\len/2}{#3}{#4}{#5}{#6} \end{scope}
\begin{scope}[rotate=-90]\caley{\newlev}{\len/2}{#6}{#3}{#4}{#5} \end{scope}
\end{scope}
}{\fill[red] circle(1pt);}
}
\begin{document}
\begin{tikzpicture}
\begin{scope}[rotate=-90] \caley{4}{4cm}{b-1}{a}{b}{a-1} \end{scope}
\begin{scope}[rotate=0] \caley{4}{4cm}{a}{b}{a-1}{b-1} \end{scope}
\begin{scope}[rotate=90] \caley{4}{4cm}{b}{a-1}{b-1}{a} \end{scope}
\begin{scope}[rotate=180] \caley{4}{4cm}{a-1}{b-1}{a}{b} \end{scope}
\end{tikzpicture}
\end{document}
答案4
这只是 MetaPost 的另一次递归尝试,与 Thruston 的不同,也不如他的好(我的标签有些粗糙),但我设法产生了一些在我看来可以接受的东西。要使用 MetaPost 进行编译并将 numbersystem 选项设置为“double”:
input latexmp; setupLaTeXMP(mode = rerun, textextlabel = enable);
u := 1cm ; % Scaling
m := 3; % Number of recursions
d := m;% Depth labelling
labeloffset := 2bp;
% the recursive macro
vardef free_group(expr loc, v, n) =
save w ; pair w; w = v rotated 90;
if (n>0):
drawdblarrow loc - v -- loc + v withcolor red;
drawarrow loc -- loc + w withcolor red;
% labels (the hard part)
if (m-n)<d:
if (xpart v) > epsilon :
label.top("$a$", loc + 0.5v);
label.rt("$b$", loc + 0.5w);
label.top("$a^{-1}$", loc - 0.5v);
elseif (xpart v) < -epsilon:
label.top("$a^{-1}$", loc + 0.5v);
label.rt("$b^{-1}$", loc + 0.5w);
label.top("$a$", loc - 0.5v);
elseif (xpart w) > epsilon:
label.rt("$b^{-1}$", loc + 0.5v);
label.top("$a$", loc + 0.5w);
label.rt("$b$", loc - 0.5v);
else:
label.top("$a^{-1}$", loc + 0.5w);
label.rt("$b$", loc + 0.5v);
label.rt("$b^{-1}$", loc - 0.5v);
fi;
fi;
free_group(loc + v, 0.5v rotated -90, n-1) ;
free_group(loc + w, 0.5v, n-1);
free_group(loc - v, 0.5v rotated 90, n-1);
fi;
enddef;
beginfig(1);
pair v, w, loc; v = (6u, 0) ; w = v rotated 90; loc = origin;
% First "star"
draw origin withpen pencircle scaled 3bp withcolor red;
drawdblarrow -v -- v withcolor red; drawdblarrow -w -- w withcolor red;
label.top("$a$", 0.5v); label.top("$a^{-1}$", -0.5v);
label.rt("$b$", 0.5w); label.rt("$b^{-1}$", -0.5w);
% Recursion calls on each apex
free_group(v, -0.5w, m);
free_group(w, 0.5v, m);
free_group(-v, 0.5w, m);
free_group(-w, -0.5v, m);
endfig;
end.
递归次数 m 和标记深度 d 均设置为 3,结果如下:
当 m = 8 且 d = 2 时,它会产生此图片。当 m=8 以上时,MetaPost 可能仍能工作,但 MPtoPDF 会失败。
编辑奇怪的是,将前面的代码包含在 LuaLaTeX 文件中比使用 MPtoPDF 得到的结果更好。使用 LuaLaTeX,我成功地产生了高达 11 级递归的结果。生成的 PDF 文件非常大:11.2 Mo,所以我不敢在这里上传它的位图版本!该程序甚至可以超过 11 级递归,但这会花费不合理的时间,而且图形的外观无论如何都不会改变。