我有一本书,里面有很多用 TikZ 生成的圆形图。这是用 XeLaTeX 编译的(我专门不想要切换到 LuaTeX)。为了简化这些图的代码,我有一系列宏,可以简化为以下内容:
\newcommand\CircArcTwo[2]{
\draw[->] (1) to[bend left=#1] (2);
\draw[->] (2) to[bend left=#2] (1);
}
\newcommand\CircArcThree[3]{
\draw[->] (1) to[bend left=#1] (2);
\draw[->] (2) to[bend left=#2] (3);
\draw[->] (3) to[bend left=#3] (1);
}
...等等,目前一直到\CircArcSeven
。重点是它们绘制弧线链接n节点:1 → 2 → 3 → ... →n→ 1. TikZ 细节与这个问题并不特别相关,实际上比我展示的要复杂得多。如果这样可以让生活更轻松,节点可以轻松重命名。
使用此方法的 MWE 如下:
\documentclass{article}
\usepackage{tikz}
\newcommand\CircArcThree[3]{
\draw[->] (1) to[bend left=#1] (2);
\draw[->] (2) to[bend left=#2] (3);
\draw[->] (3) to[bend left=#3] (1);
}
\begin{document}
\begin{tikzpicture}
\node (1) at (90:1) {Foo};
\node (2) at (345:1) {Bar};
\node (3) at (195:1) {Baz};
\CircArcThree{32}{55}{32}
\end{tikzpicture}
\end{document}
有没有一种好的方法可以将其写成一个采用可变数量参数的宏,或者以某种方式接受一个列表作为其参数,这样\CircArcThree{32}{55}{32}
我就可以使用一个宏而不是写,也许可以写\CircArc{32,55,32}
?
我并不关心语法到底是什么;我的主要目的是删除重复的代码,因为我最近发现我的\CircArcSix
宏中的一个弧定义存在错误,而这个宏是该集合中使用最少的。
答案1
也许这可以做到\foreach
,但我更放心expl3
。
\documentclass{article}
\usepackage{tikz}
\ExplSyntaxOn
\NewDocumentCommand\CircArc{m}
{
\richard_circarc:n { #1 }
}
\cs_new_protected:Nn \richard_circarc:n
{
% split the list into a sequence
\seq_set_from_clist:Nn \l__richard_circarc_seq { #1 }
% clear the tl containing the body
\tl_clear:N \l__richard_circarc_tl
% add the arcs
\seq_map_indexed_function:NN \l__richard_circarc_seq \__richard_circarc_make:nn
% deliver the result
\tl_use:N \l__richard_circarc_tl
}
\cs_new_protected:Nn \__richard_circarc_make:nn
{
\int_compare:nTF { #1 = \seq_count:N \l__richard_circarc_seq }
{% we're at the last arc, we need to go back to 1
\__richard_circarc_make_aux:nnn { #1 } { 1 } { #2 }
}
{% we're in the middle, go to the next node
\__richard_circarc_make_aux:nen { #1 } { \int_eval:n { #1 + 1 } } { #2 }
}
}
\cs_new_protected:Nn \__richard_circarc_make_aux:nnn
{
\tl_put_right:Nn \l__richard_circarc_tl
{
\draw[->]~(#1)~to~[bend~left=#3]~(#2);
}
}
\cs_generate_variant:Nn \__richard_circarc_make_aux:nnn { ne }
\ExplSyntaxOff
\begin{document}
\begin{tikzpicture}
\node (1) at (90:1) {Foo};
\node (2) at (345:1) {Bar};
\node (3) at (195:1) {Baz};
\CircArc{32,55,32}
\end{tikzpicture}
\begin{tikzpicture}
\node (1) at (90:1) {Foo};
\node (2) at (0:1) {Bar};
\node (3) at (270:1) {Baz};
\node (4) at (180:1) {Gnu};
\CircArc{32,32,32,32}
\end{tikzpicture}
\end{document}
答案2
感谢 David Carlisle 建议我使用\foreach
。这似乎是一个可行的解决方案:
\documentclass{article}
\usepackage{tikz}
\newcounter{arccount}
\newcommand\CircArc[1]{
\setcounter{arccount}{0}
\foreach \x in {#1}{\stepcounter{arccount}}
\foreach \x [count=\n from 1] in {#1}{
\pgfmathparse{int(mod(\n,\thearccount)+1)}
\draw[->] (\n) to[bend left=\x] (\pgfmathresult);
}
}
\begin{document}
\begin{tikzpicture}
\node (1) at (90:1) {Foo};
\node (2) at (345:1) {Bar};
\node (3) at (195:1) {Baz};
\CircArc{32,55,32}
\end{tikzpicture}
\end{document}
对我来说,这似乎比基于 LaTeX3 的解决方案更清晰,尽管这可能只是因为我缺乏使用 LaTeX3 功能的经验。我需要定义一个计数器来获取列表的长度,这似乎有点不雅,但这是一个足够简单的解决方案。
答案3
下面实现了一个自动化程序,它不仅可以从参数列表中绘制弧,还可以放置名为的节点\circarc
。
该语法使用一个 key=value 列表,其中的键是节点文本,值是另一个 key=value 列表。在第二个列表中,可以使用以下键:
bend
赋予的价值bend left
angle
节点放置的角度(否则自动确定)radius
节点所在半径style
可含 Ti钾给予\node
ccw
逆时针旋转(不采用值)cw
顺时针旋转(不采用值)
此外,您还可以使用angle += <num>
或angle -= <num>
为自动计算的角度提供偏移。
所有这些键也可以在可选的 中给出\circarc
,唯一的区别是angle
表示第一个节点的角度(其他节点随后会相对于该角度自动放置,此起始角度的默认值为 0°,因为\circarc@angle
最初为空,在此用法中+
或-
表示法没有区别)。您还可以bend
以简短表示法给出:key=value 列表中没有等号的任何元素都被解释为(和bend
除外)。cw
ccw
节点circarc-<n>
以<n>
key=value 列表中从 0 开始的索引命名。
这用于个人偏好(免责声明:我是作者),但当然expkv
您也可以使用来设置键。虽然解析顶级列表需要 (您可以改用)。pgfkeys
expkv
\ekvparse
expl3
\keyval_parse:NNn
\documentclass{article}
\usepackage{tikz}
\usepackage{expkv-def}
\makeatletter
\ekvsetdef\circarcSetup{richard/circarc}
\ekvdefinekeys{richard/circarc}
{%
store bend = \circarc@bend
,store radius = \circarc@radius
,initial radius = 1
,store angle = \circarc@angle
,store style = \circarc@style
% counter-clockwise
,noval ccw = \def\circarc@rotdir{+}\def\circarc@benddir{right}
% clockwise
,noval cw = \def\circarc@rotdir{-}\def\circarc@benddir{left}
,initial cw
,unknown noval = \edef\circarc@bend{\unexpanded{#1}}
}
% shortcoming in current version of exkpv-def, no interface to expkv's `\ekvlet`
% has been added, yet.
\ekvletkv{richard/circarc}{angle-}{richard/circarc}{angle}
\ekvletkv{richard/circarc}{angle+}{richard/circarc}{angle}
\ekvdefinekeys{richard/circarc}
{%
also code angle- = \def\circarc@sign{-}
,also code angle+ = \def\circarc@sign{+}
}
% so that `angle -= 10` and `angle-=10` do the same thing
\ekvletkv{richard/circarc}{angle -}{richard/circarc}{angle-}
\ekvletkv{richard/circarc}{angle +}{richard/circarc}{angle+}
\newcount\circarc@elements
\newcount\circarc@current
\newcommand*\circarc@sign{}
\newcommand\circarc@count[2]{#1=\the\numexpr\ekvcsvloop{+1\@gobble}{#2}\relax}
\newcommand\circarc@once[1]{\unexpanded\expandafter{#1}}
\protected\def\circarc@smuggle\endgroup
{%
\expanded
{%
\endgroup
\edef\noexpand\circarc@output
{\noexpand\unexpanded{\circarc@once\circarc@output}}%
}%
}
\newcommand\circarc@eaddto[2]
{\edef#1{\unexpanded\expandafter{#1}#2}}
\newcommand\circarc@epreto[2]
{\edef#1{#2\unexpanded\expandafter{#1}}}
\newcommand\circarc[2][]
{%
\begingroup
\circarcSetup{#1}%
\let\circarc@startangle\circarc@angle
\let\circarc@angle\@empty
\let\circarc@sign\@empty
\let\circarc@output\@empty
\circarc@current=\z@
\circarc@count\circarc@elements{#2}%
\ekvparse\circarc@add@n@opts\circarc@add@w@opts{#2}%
\expandafter
\endgroup
\circarc@output
}
\newcommand\circarc@add@w@opts[2]
{%
\begingroup
\let\circarc@angle\@empty
\circarcSetup{#2}%
\circarc@add{#1}%
\circarc@smuggle
\endgroup
\advance\circarc@current by\@ne
}
\newcommand\circarc@add@n@opts[1]
{%
\begingroup
\circarc@add{#1}%
\circarc@smuggle
\endgroup
\advance\circarc@current by\@ne
}
\newcommand\circarc@add[1]
{%
\circarc@epreto\circarc@output
{%
\noexpand\node[\circarc@once\circarc@style]
(circarc-\the\circarc@current)
at (%
\ifx\circarc@angle\@empty
\fpeval
{%
\circarc@startangle
\circarc@rotdir\circarc@current*360/\circarc@elements
}%
\else
\ifx\circarc@sign\@empty
\circarc@once\circarc@angle
\else
\fpeval
{%
\circarc@startangle
\circarc@rotdir\circarc@current*360/\circarc@elements
\circarc@sign\circarc@angle
}%
\fi
\fi
:\circarc@once\circarc@radius
)
{\unexpanded{#1}};
}%
\circarc@eaddto\circarc@output
{%
\noexpand\draw[->]
(circarc-\the\circarc@current)
to[%
bend \circarc@benddir
\ifx\circarc@bend\@empty\else=\circarc@once\circarc@bend\fi
]
(%
circarc-%
\ifnum\numexpr\circarc@current+\@ne=\circarc@elements
0%
\else
\the\numexpr\circarc@current+\@ne\relax
\fi
);%
}%
}
\makeatother
\begin{document}
\begin{tikzpicture}
\circarc[angle=90]
{%
Foo,Bar=150,Baz={radius=1.5,bend=15}
}
\begin{scope}[xshift=3cm]
\circarc[radius=1.2,ccw]
{
Foo={angle=-15},Bar,Baz={style=draw},Bang={angle-=25}
}
\end{scope}
\end{tikzpicture}
\end{document}