以可变长度列表作为参数的宏

以可变长度列表作为参数的宏

我有一本书,里面有很多用 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除外)。cwccw

节点circarc-<n><n>key=value 列表中从 0 开始的索引命名。

这用于个人偏好(免责声明:我是作者),但当然expkv您也可以使用来设置键。虽然解析顶级列表需要 (您可以改用)。pgfkeysexpkv\ekvparseexpl3\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}

在此处输入图片描述

相关内容