我想创建一个 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
来为我们完成此操作。本质上,我们用 替换carc
,arc
这意味着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 后面的字符c
是i
,TikZ 会假设您指的是circle
关键字。如果是h
,它会假设您想要child
树中的 。如果两者都不是,TikZ 会假设您想要 或cosine
,coordinate
因此它会将控制权传递给\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 path
。to
路径允许您指定相当任意的代码,以将路径的下一部分替换为相当复杂的东西。您可以在网站上搜索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}