如何扩展 TikZ 绘图命令

如何扩展 TikZ 绘图命令

我想创建一个 carc 路径宏(居中圆弧,其作用类似于圆,但不改变当前位置),以便能够写入:

\draw (0,0) carc(30:90:1) carc(30:90:2) carc(30:90:3);

我知道如何用乳胶宏来做到这一点:

\def\carc(#1:#2:#3){++(#1:#3) arc(#1:#2:#3) ++(#2+180:#3)}

然后我可以使用:

\draw (0,0) \carc(30:90:1) \carc(30:90:2) \carc(30:90:3);

但我想知道,是否有办法在纯 TikZ 中做到这一点?

这是一种在路径上创建类似圆形、矩形、圆弧等新宏的方法吗?

答案1

这是一个适用于的替代解决方案

\draw (0,0) carc(30:90:1) carc(30:90:2) carc(30:90:3);

并允许充分利用所arc采用的标准选项。它使用 Jake 的技巧来扩展解析器,但我们没有插入全新的代码,而是颠覆命令arc来为我们完成此操作。本质上,我们用 替换carcarc这意味着carc可以接受所有 的参数arc,但是当arc实际向路径添加某些内容时(在计算出所有坐标之后),我们会偷偷地添加几个 moveto 以确保路径的中心位于我们想要的位置。这也可以工作,而无需颠覆解析器,只需在[centre arc]调用 之前放置 即可arc。(事实上,carc通过扩展为 来工作[centre arc] arc。)

我仍然强烈推荐该to path解决方案。除非您知道自己在做什么,否则不要破坏 TikZ 代码。

\documentclass{article}
%\url{http://tex.stackexchange.com/q/34973/86}

\usepackage{tikz}

\makeatletter
\def\tikz@@@carcfinal#1#2#3{%
  \pgf@process{#2}%
  \advance\pgf@x by \tikz@lastx
  \advance\pgf@y by \tikz@lasty
  \pgfpathmoveto{\pgfqpoint{\the\pgf@x}{\the\pgf@y}}%
  #1%
  \pgfpathmoveto{\pgfqpoint{\the\tikz@lastx}{\the\tikz@lasty}}%
  \let\tikz@@@arcfinal=\carc@orig@@@arcfinal
}

\def\tikz@carcfinal{%
  \tikz@lastxsaved=\tikz@lastx%
  \tikz@lastysaved=\tikz@lasty%
  \let\tikz@arcfinal=\carc@orig@arcfinal
  \tikz@scan@next@command%
}


\let\carc@orig@@@arcfinal=\tikz@@@arcfinal
\let\carc@orig@arcfinal=\tikz@arcfinal

\def\tikz@cchar{% If keyword starts with "c..."
    \pgfutil@ifnextchar i %... starts with "ci..."
        {\tikz@circle}%
        {\pgfutil@ifnextchar h% ... starts with "ch..."
            {\tikz@children}
            {\pgfutil@ifnextchar a % ... starts with "ca..."
                {\carc@call}
                \tikz@cochar
            }
        }
}%

\def\carc@call{\tikzset{centred arc}\tikz@scan@next@command}

\tikzset{
  centred arc/.code={%
    \let\tikz@@@arcfinal=\tikz@@@carcfinal
    \let\tikz@arcfinal=\tikz@carcfinal
  },
}
\makeatother

\begin{document}
\begin{tikzpicture}
\draw (0,0) carc[start angle=30,end angle=90,radius=1] carc (30:90:2) carc(30:90:3);
\end{tikzpicture}
\end{document}

请注意 s 的不同语法carc和不同数量的空格。

中心圆弧

答案2

Martin 是对的,你需要重写解析器的部分内容。不过这并不难。解析器位于 中tikz.code.tex。有一个宏叫做\tikz@cchar,它处理所有以 开头的关键字c。它包含嵌套\pgfutil@ifnextchar语句,充当“选择”表达式:它们查看关键字中的下一个字母并调用相应的宏。

这是原始\tikz@cchar定义:

\def\tikz@cchar{%
  \pgfutil@ifnextchar i{\tikz@circle}%
  {\pgfutil@ifnextchar h{\tikz@children}{\tikz@cochar}}}%

如果 a 后面的字符ci,TikZ 会假设您指的是circle关键字。如果是h,它会假设您想要child树中的 。如果两者都不是,TikZ 会假设您想要 或cosinecoordinate因此它会将控制权传递给\tikz@cochar,然后 以类似的方式决定两个关键字中的哪一个存在。

为了让解析器识别carc,我们可以\tikz@cchar像这样重新定义宏:

\makeatletter
\def\tikz@cchar{% If keyword starts with "c..."
    \pgfutil@ifnextchar i %... starts with "ci..."
        {\tikz@circle}%
        {\pgfutil@ifnextchar h% ... starts with "ch..."
            {\tikz@children}
            {\pgfutil@ifnextchar a % ... starts with "ca..."
                {\carc}
                \tikz@cochar
            }
        }
}%

\carc需要稍微重新定义一下。语法不是\carc (#1:#2:#3),而是\carc arc (#1:#2:#3),因为c是实际调用宏的实体,并且 仍存arc在于宏参数中。另外,我们不能直接在代码中使用 TikZ 命令(PGF 命令可以工作)。TikZ 命令可以通过将它们包装在 中来使用,这会直接插入所需的代码。最后,我们需要通过在宏末尾\tikzset{insert path={<TikZ code>}}发出 来告诉 TikZ 解析器返回查找关键字。\tikz@scan@next@command\carc

这是一个完整的例子:

\documentclass[12pt]{article}
\usepackage{tikz}
\begin{document}

\makeatletter
\def\tikz@cchar{% If keyword starts with "c..."
    \pgfutil@ifnextchar i %... starts with "ci..."
        {\tikz@circle}%
        {\pgfutil@ifnextchar h% ... starts with "ch..."
            {\tikz@children}
            {\pgfutil@ifnextchar a % ... starts with "ca..."
                {\carc}
                \tikz@cochar
            }
        }
}%

\def\carc arc (#1:#2:#3){% The macro will get called after the initial "c" of "carc" is already gobbled up, and only "arc" is left
    \tikzset{insert path={++(#1:#3) arc(#1:#2:#3) ++(#2+180:#3)}}% Inject the TikZ commands
    \tikz@scan@next@command% Return control to the parser
}
\makeatother

\begin{tikzpicture}%
\draw (0,0) carc (30:90:1) carc (30:90:2) carc (30:90:3);
\end{tikzpicture}%
\end{document}

答案3

杰克和马丁是对的,如果你真的必须

\draw (0,0) carc(30:90:1) carc(30:90:2) carc(30:90:3);

那么您唯一的选择就是重新定义解析器。

但是,TikZ 确实有一种标准方法来定义相当通用的替代路径,而不需要这种繁重的黑客攻击。这就是语法to pathto路径允许您指定相当任意的代码,以将路径的下一部分替换为相当复杂的东西。您可以在网站上搜索to path来了解一下这是有可能实现的。

就你的情况而言,代码非常简单。但它确实有一个隐蔽之处: a 的目标to path未经处理就被保存,这意味着我们可以按照自己的喜好来解释它。我们不是指定坐标,而是使用它来指定命令的参数arc。因此,我们必须解析这些数据,并根据我们将如何使用它来拆分它。还有其他方法可以做到这一点,但由于我们必须为 a 指定一个目标to path,我们不妨使用它作为传递信息的一种方式。

这是代码。我添加了您的宏以供比较,以便您看到它确实做了同样的事情。在将您的宏转换为时,我做了两处更改to path:我使用了命令的较新语法arc,并且我使用了\tikztostart命令来确保我们返回到我们来的地方。这两项都是外观上的更改,旨在使代码更清晰,以便在 6 个月后您仍然可以理解它在做什么,而不必费解。

\documentclass{article}

\usepackage{tikz}

\def\carcArgs#1:#2:#3\carcSTOP{%
  \def\carcOne{#1}%
  \def\carcTwo{#2}%
  \def\carcThree{#3}%
}

\tikzset{
  centred arc/.style={%
    to path={%
      \pgfextra{%
        \expandafter\carcArgs\tikztotarget\carcSTOP
      }
      ++(\carcOne:\carcThree) arc[start angle=\carcOne, end angle=\carcTwo, radius=\carcThree] (\tikztostart)
    }
  }
}
\def\carc(#1:#2:#3){++(#1:#3) arc(#1:#2:#3) ++(#2+180:#3)}

\begin{document}
\begin{tikzpicture}
\draw (0,0) -- (1,0) \carc(30:90:1) -- (2,-1);
\begin{scope}[yshift=-2cm,red]
\draw (0,0) -- (1,0) to[centred arc] (30:90:1) -- (2,-1);
\end{scope}
\end{tikzpicture}
\end{document}

这是结果,黑线是你的宏,红线是我的:

中心圆弧

答案4

您可以定义carc使用

\documentclass{article}
\usepackage{tikz}
\tikzset{
  carc/.style args={#1:#2:#3}{
    insert path={+(#1:#3) arc (#1:#2:#3)}
  }
}

\begin{document}
\begin{tikzpicture}
\draw (0,0) [carc=30:90:1] (0,0) [carc=30:90:2] (0,0) [carc=30:90:3];
\end{tikzpicture}
\end{document}

该 API 比您的稍微复杂一些:您必须分别给出每个圆弧的中心坐标。

如果你发现经常需要它们,你也可以扩展它来创建半圆和四分之一圆,例如

\documentclass[class=article,tikz]{standalone}

\tikzset{
  carc/.style args={#1:#2:#3}{
    insert path={+(#1:#3) arc (#1:#2:#3)}
  }
}

\tikzset{
  semicircle/.style args={#1:#2}{
    insert path={+(#1:#2) arc (#1:#1+180:#2)}
  }
}

\tikzset{
  quartercircle/.style args={#1:#2}{
    insert path={+(#1:#2) arc (#1:#1+90:#2)}
  }
}

\begin{document}
\begin{tikzpicture}[thick,scale=2]
\draw [red] (0,0) [carc=30:90:1];
\draw [green!60!black] (0.25,0.25) [quartercircle=30:1];
\draw [blue] (0.5,0.5) [semicircle=30:1];
\end{tikzpicture}

\end{document}

在此处输入图片描述

相关内容