当我从一个框的角到另一个框的同一角画一条粗线时,它无法正确连接。请参阅下面的代码和图片:
\documentclass[a4paper, 11pt]{article}
\usepackage{tikz}
\usetikzlibrary{shapes,arrows}
\usetikzlibrary{positioning}
\begin{document}
\begin{tikzpicture}
\node [draw, thick, minimum size=1cm] (a) {A};
\node [draw, thick, minimum size=3cm, right=2cm of a] (b) {B};
\draw [thick, dashed] (a.north west) -- (b.north west);
\draw [thick, dashed] (a.north east) -- (b.north east);
\draw [thick, dashed] (a.south west) -- (b.south west);
\draw [thick, dashed] (a.south east) -- (b.south east);
\end{tikzpicture}
\end{document}
我怎样才能使线条准确地附着在角落上而不是像本例中那样稍微偏离?
答案1
非常好的问题!
首先,解释一下。当 TikZ/PGF 创建节点时,它会将各种相关位置存储为“锚点”。这些可用于指代节点上除其主坐标(其中心)之外的点。一个非常常见的锚点系列是节点的边界,这些可以通过角度来引用,如(node.45)
或通过罗盘方向来引用,如(node.north west)
。虽然节点可以根据自己的喜好为这些定义规则,但基本节点倾向于将这些规则定位为节点边界上合理的点选择(“合理的”示例:矩形节点上(node.north west)
始终有一个角)。
这里的关键词是“在节点的边界上“。这里的线宽至关重要。如果你写:
\node[red,draw, minimum size=2cm] (a) {};
\draw[<-] (a) -- ++(5,0);
那么你想让箭头停在边缘边界,而不是边界的中间。如果我们把线宽弄得可笑,那么我们可以看到效果:
箭头停在节点边界的边缘,而不是中点。要获得中点,我们必须将线延长一半的线宽。然而,这不是一个好的解决方案,因为我们需要使用绘制节点时有效的线宽(可能不是当前线宽),并且只有当线与边界正交时它才有效,否则我们需要调整偏移量。
如果节点本身知道这个“中间边界”,那就更好了。我马上想到了两种方法。一种是定义一个新的节点形状,其中锚点边界不受线宽调整。这相当容易做到:从正确的文件复制定义并进行必要的调整,但我不建议将其用于一次性图片或不满意的人。\makeatletter ... \makeatother
如果你可能会经常使用这个,那就说出来,如果你需要帮助,我会帮你修改。
一种更快(也更粗暴)的方法是将边框的绘制与设置锚点的用途分开。有两种方法可以做到这一点:使用两个节点,或使用一个带有后置或前置操作的节点。在第一种情况下,您定义一个未绘制但定义形状的节点。然后,您将一个绘制的新节点放在顶部。然后,您引用第一的对于锚点。第二种情况实际上是一样的,只是你将命令合并为一个,这样可以避免创建额外的对象和不必要的命令重复。
以下是按照第一种方式完成的上述示例:
\node[minimum size=2cm,postaction={draw,thick,red},line width=0cm] (a) {};
\draw[<-] (a) -- ++(5,0);
我们确实需要在操作中指定线宽,即使它是预操作,因为会line width=0cm
立即设置。可以定义一种保存当前线宽并在操作中恢复它的方法。
以下是具有两个节点的相同示例:
\node[minimum size=2cm,line width=0cm] (a) {};
\node[draw,red,minimum size=2cm] at (a) {};
\draw[<-] (a) -- ++(5,0);
这一点很重要,就是要确保第一个节点上的任何大小的命令都得到复制。如果节点的大小由其内容决定,那么这将会变得困难。
这两种方法都会产生以下结果:
现在我们可以看到箭头到达了它应该到达的地方。
如果我们将其添加到您的代码中,我们就可以看到它的效果。为了强调效果,我将线宽增加到 1mm,并将虚线绘制为实心红色。
(请注意这种锚点移动的另一个效果。由于右边的方块是相对于第一个方块定位的,改变节点边界会稍微改变它的位置。)
为了使编码更容易,我按照上面的建议做了,并定义了一些键。因此,节点本身定义为:
\node [draw with anchor in boundary, minimum size=1cm] (a) {A};
完整代码(针对以上两个版本)是:
\documentclass[a4paper, 11pt]{article}
%\url{http://tex.stackexchange.com/q/29874/86}
\usepackage{tikz}
\usetikzlibrary{shapes,arrows}
\usetikzlibrary{positioning}
\newdimen\zerolinewidth
\tikzset{
zero line width/.code={%
\zerolinewidth=\pgflinewidth
\tikzset{line width=0cm}%
},
use line width/.code={%
\tikzset{line width=\the\zerolinewidth}%
},
draw with anchor in boundary/.style={
zero line width,
postaction={draw,use line width},
},
}
\begin{document}
\begin{tikzpicture}[line width=1mm]
\node [draw, minimum size=1cm] (a) {A};
\node [draw, minimum size=3cm, right=2cm of a] (b) {B};
\draw [red] (a.north west) -- (b.north west);
\draw [red] (a.north east) -- (b.north east);
\draw [red] (a.south west) -- (b.south west);
\draw [red] (a.south east) -- (b.south east);
\end{tikzpicture}
\begin{tikzpicture}[line width=1mm]
\node [draw with anchor in boundary, minimum size=1cm] (a) {A};
\node [draw with anchor in boundary, minimum size=3cm, right=2cm of a] (b) {B};
\draw [red] (a.north west) -- (b.north west);
\draw [red] (a.north east) -- (b.north east);
\draw [red] (a.south west) -- (b.south west);
\draw [red] (a.south east) -- (b.south east);
\end{tikzpicture}
\end{document}
更新:preaction
或的选择postaction
最初是任意的,但看到更新的问题后,我意识到我选错了。它应该(就像现在一样)是事后行动。这样,如果节点被填充(这可以在主节点上完成,因为它不受线宽的影响),然后绘制,那么绘制应该完成第二使其位于填充之上。否则填充将占用线宽的一半(如更新的问题中所示)。
答案2
outer sep=0pt
为什么不对第一个节点使用,默认情况下,我认为这足以获得良好的结果。最后两张图片与或outer sep=0.5\pgflinewidth
相同draw with anchor in boundary
outer sep=0pt
\documentclass[a4paper, 11pt]{article}
%\url{http://tex.stackexchange.com/q/29874/86}
\usepackage{tikz}
\usetikzlibrary{shapes,arrows}
\usetikzlibrary{positioning}
\newdimen\zerolinewidth
\tikzset{
zero line width/.code={%
\zerolinewidth=\pgflinewidth
\tikzset{line width=0cm}%
},
use line width/.code={%
\tikzset{line width=\the\zerolinewidth}%
},
draw with anchor in boundary/.style={
zero line width,
postaction={draw,use line width},
},
}
\begin{document}
\begin{tikzpicture}
\node [draw, thick, minimum size=1cm,outer sep=0pt] (a) {A};
\node [draw, thick, minimum size=3cm, right=2cm of a,outer sep=0pt] (b) {B};
\draw [thick, dashed] (a.north west) -- (b.north west);
\draw [thick, dashed] (a.north east) -- (b.north east);
\draw [thick, dashed] (a.south west) -- (b.south west);
\draw [thick, dashed] (a.south east) -- (b.south east);
\end{tikzpicture}
\begin{tikzpicture}[line width=1mm]
\node [draw, minimum size=1cm,outer sep=0pt] (a) {A};
\node [draw, minimum size=3cm, right=2cm of a,outer sep=0pt] (b) {B};
\draw [red] (a.north west) -- (b.north west);
\draw [red] (a.north east) -- (b.north east);
\draw [red] (a.south west) -- (b.south west);
\draw [red] (a.south east) -- (b.south east);
\end{tikzpicture}
\begin{tikzpicture}[line width=1mm]
\node [draw with anchor in boundary, minimum size=1cm] (a) {A};
\node [draw with anchor in boundary, minimum size=3cm, right=2cm of a] (b) {B};
\draw [red] (a.north west) -- (b.north west);
\draw [red] (a.north east) -- (b.north east);
\draw [red] (a.south west) -- (b.south west);
\draw [red] (a.south east) -- (b.south east);
\end{tikzpicture}
\end{document}
答案3
安德鲁·史黛西很好地解释了为什么这些线只接触节点的边界,并且阿兰·马特斯给出了一个在这种简单情况下更优越(并且本质上相同)的解决方案。
我想给出另一种解决方案,它不依赖于outer sep
s 设置,实际上也不依赖于原始锚点(边界上的原始锚点会丢失,甚至一条简单的\draw (a) -- (b);
线也会延伸到矩形的边框中)。
灵感来自马丁·沙雷尔对问题的回答要求更多的锚点我实际上在rectangular
形状中添加了更多锚点。
所有位于边界上的锚点(所有罗盘点以及.mid west
、.base west
和).mid east
都.base east
被复制到line <old anchor name>
,例如.line north west
、.line north
等等.line mid east
。
就像所有原始锚点都基于两个“已保存”锚点(即north east
和south west
)一样,“线”锚点基于line north east
和 ,其计算非常简单:它们的定义与和line south west
几乎相同;只是和的值未被使用。north east
south west
outer xsep
outer ysep
在这个答案中给出了三个输出:
- 使用问题代码给出的设置
- 具有额外的“调试”设置(较粗和实线,不透明度)
- 以及 PGF 手册 (第 48.2 节“预定义形状”,第 420 页) 的矩形图形的缩小克隆,显示了新的锚点及其旧的伴随物。
代码
\documentclass[tikz]{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes,arrows}
\usetikzlibrary{positioning}
\makeatletter
% The macro \pgfaddtoshape is originally from Martin Scharrer
& https://tex.stackexchange.com/a/14772/16595
% and here extended to be able to use \savedanchor and \nodeparts/\pgfnodepartstextbox
\def\pgfaddtoshape#1#2{%
\begingroup
\def\pgf@sm@shape@name{#1}%
\let\savedanchor=\pgf@sh@savedanchor
\let\anchor=\pgf@sh@anchor
\let\nodeparts=\pgf@sh@boxes
\nodeparts{text}%
#2%
\endgroup
}
%
\pgfaddtoshape{rectangle}{
\savedanchor\linesouthwest{%
\pgf@x=\wd\pgfnodeparttextbox%
\pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/inner xsep}}%
\advance\pgf@x by 2\pgf@xc%
\pgfmathsetlength\pgf@xb{\pgfkeysvalueof{/pgf/minimum width}}%
\ifdim\pgf@x<\pgf@xb%
\pgf@x=\pgf@xb%
\fi%
\pgf@x=-.5\pgf@x%
\advance\pgf@x by.5\wd\pgfnodeparttextbox%
\pgf@y=\ht\pgfnodeparttextbox%
\advance\pgf@y by\dp\pgfnodeparttextbox%
\pgfmathsetlength\pgf@yc{\pgfkeysvalueof{/pgf/inner ysep}}%
\advance\pgf@y by 2\pgf@yc%
\pgfmathsetlength\pgf@yb{\pgfkeysvalueof{/pgf/minimum height}}%
\ifdim\pgf@y<\pgf@yb
\pgf@y=\pgf@yb
\fi%
\pgf@y=-.5\pgf@y%
\advance\pgf@y by-.5\dp\pgfnodeparttextbox%
\advance\pgf@y by.5\ht\pgfnodeparttextbox%
}
\anchor{line south west}{\linesouthwest}
\savedanchor\linenortheast{%
\pgf@x=\the\wd\pgfnodeparttextbox%
\pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/inner xsep}}%
\advance\pgf@x by 2\pgf@xc%
\pgfmathsetlength\pgf@xb{\pgfkeysvalueof{/pgf/minimum width}}%
\ifdim\pgf@x<\pgf@xb%
\pgf@x=\pgf@xb%
\fi%
\pgf@x=.5\pgf@x%
\advance\pgf@x by.5\wd\pgfnodeparttextbox%
\pgf@y=\ht\pgfnodeparttextbox%
\advance\pgf@y by\dp\pgfnodeparttextbox%
\pgfmathsetlength\pgf@yc{\pgfkeysvalueof{/pgf/inner ysep}}%
\advance\pgf@y by 2\pgf@yc%
\pgfmathsetlength\pgf@yb{\pgfkeysvalueof{/pgf/minimum height}}%
\ifdim\pgf@y<\pgf@yb%
\pgf@y=\pgf@yb%
\fi%
\pgf@y=.5\pgf@y%
\advance\pgf@y by-.5\dp\pgfnodeparttextbox%
\advance\pgf@y by.5\ht\pgfnodeparttextbox%
}
\anchor{line north east}{\linenortheast}
\anchor{line north west}{
\pgf@process{\linesouthwest}
\pgf@xa=\pgf@x
\pgf@process{\linenortheast}
\pgf@x=\pgf@xa
}
\anchor{line south east}{
\pgf@process{\linesouthwest}
\pgf@ya=\pgf@y
\pgf@process{\linenortheast}
\pgf@y=\pgf@ya
}
\anchor{line north}{
\pgf@process{\linesouthwest}%
\pgf@xa=.5\pgf@x%
\pgf@process{\linenortheast}%
\pgf@x=.5\pgf@x%
\advance\pgf@x by \pgf@xa%
}
\anchor{line south}{
\pgf@process{\linenortheast}%
\pgf@xa=.5\pgf@x%
\pgf@process{\linesouthwest}%
\pgf@x=.5\pgf@x%
\advance\pgf@x by \pgf@xa%
}
\anchor{line west}{
\pgf@process{\linenortheast}%
\pgf@ya=.5\pgf@y%
\pgf@process{\linesouthwest}%
\pgf@y=.5\pgf@y%
\advance\pgf@y by \pgf@ya%
}
\anchor{line mid west}{\linesouthwest\pgfmathsetlength\pgf@y{.5ex}}
\anchor{line base west}{\linesouthwest\pgf@y=0pt}
\anchor{line east}{%
\pgf@process{\linesouthwest}%
\pgf@ya=.5\pgf@y%
\pgf@process{\linenortheast}%
\pgf@y=.5\pgf@y%
\advance\pgf@y by \pgf@ya%
}
\anchor{line mid east}{\linenortheast\pgfmathsetlength\pgf@y{.5ex}}
\anchor{line base east}{\linenortheast\pgf@y=0pt}
}
\makeatother
\begin{document}
\begin{tikzpicture}[
% thick/.style={line width=10pt},% debug
% dashed/.style={solid},% debug
% opacity=.5
]
\node [draw, thick, minimum size=1cm] (a) {A};
\node [draw, thick, minimum size=3cm, right=2cm of a] (b) {B};
\draw [thick, dashed] (a.line north west) -- (b.line north west);
\draw [thick, dashed] (a.line north east) -- (b.line north east);
\draw [thick, dashed] (a.line south west) -- (b.line south west);
\draw [thick, dashed] (a.line south east) -- (b.line south east);
\end{tikzpicture}
\end{document}