以下 MWE 是我正在编写的库中一些代码的非常抽象的版本:
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}
\node[draw] at (1,0) (A) {A};
\coordinate (connection) at ($(A.north west)!0.66!(A.south west)$);
\path[draw] (0,0) -- (connection);
\end{tikzpicture}
\end{document}
我要做的是将“A”节点和“连接”坐标稍微向上移动,以使线变得完全水平。
以前这没有问题,因为我事先知道节点高度,我可以使用 calc 库来计算正确的 yshift,然后再绘制节点和坐标。但是,现在我想以一种依赖于节点高度的方式来执行此操作,而节点高度可能取决于其内容。所以看起来我必须先绘制节点,然后再计算其高度,然后移动它。
我知道我可以动态地获取高度,正如如何获取 TikZ 节点的高度并将其存储在长度中?,但我不知道是否可以在绘制节点后移动它。
或者,有没有办法简单地说“将节点的锚点定位在其西北到西南的正好 2/3 处的点?”如果可以做到这一点(对于任意分数),它将解决我的问题。
答案1
通用锚点提供了一个简单的界面,用于向(所有)形状添加新锚点。虽然在这种情况下,它们对于圆形节点没有多大意义,但仅仅将它们添加到形状rectangle
(或根据 声明新形状rectangle
)会将这些新锚点限制在该形状上,而它们适用于所有矩形形状。
在这种情况下,所有节点将始终具有所有生成的通用锚点(针对特定范围)。
这并没有添加水平线,为了使其成为形状的一部分,我们确实必须声明一个新形状。(我们也可以再次作弊并即时调整形状的路径,但此时正确的形状声明要清晰得多。)您可能想了解\deferredanchor
这使得锚点具有更大的灵活性。
然而,各种电路形状TikZ 和circuitkz
正在创建一个具有多个实例的形状,这些实例具有不同数量的输入锚点。如果输入被否定,它们还会绘制一些额外的位(一个圆圈)。
您需要对它们进行调整,以将直线作为动态生成的形状变化的一部分。
代码
\documentclass[tikz]{standalone}
\makeatletter
\pgfset{
@generate anchor/.code args={#1:#2:#3:#4}{%
\pgfdeclaregenericanchor{#1}{%
\pgfpointlineattime{#2}
{\pgf@sh@reanchor{##1}{#3}}{\pgf@sh@reanchor{##1}{#4}}}},
generate anchors/.style n args={3}{
/utils/tempa/.style={/pgf/@generate anchor={##1:#2:#3}},
/utils/tempa/.list={#1}}}
\makeatother
\pgfset{generate anchors={in 1/2:.3333, in 2/2:.6667}{north west}{south west}}
\begin{document}
\begin{tikzpicture}
\node[draw, anchor=in 2/2] at (1, 0) (A) {A};
\draw (0,0) -- (A.in 2/2);
\end{tikzpicture}
\begin{tikzpicture}[
shape example/.style={
color=black!30, draw, fill=yellow!30,
line width=+.5cm, inner xsep=+2.5cm, inner ysep=0.5cm},
generate anchors=
{in 1/3:.25, in 2/3:.5, in 3/3:.75}{north west}{south west},
generate anchors=
{out 1/4:.2, out 2/4:.4, out 3/4:.6, out 4/4:.8}{north east}{south east}]
\node[shape example] (s) {In Out Rectangle\vrule width 1pt height 2cm};
\foreach \anchor/\placement in
{north west/above left, north/above, north east/above right,
% west/left, center/above, east/right,
% mid west/right, mid/above, mid east/left,
% base west/left, base/below, base east/right,
south west/below left, south/below, south east/below right,
% text/left, 10/right, 130/above,
{in 1/3}/left, {in 2/3}/left, {in 3/3}/left,
{out 1/4}/right, {out 2/4}/right, {out 3/4}/right, {out 4/4}/right}
\draw[shift=(s.\anchor)] plot[mark=x] coordinates{(0,0)}
node[\placement] {\scriptsize\texttt{(s.\anchor)}};
\end{tikzpicture}
\begin{tikzpicture}[
my anchors/.style = {
generate anchors={in 1/2:.3333, in 2/2:.6667}{north west}{south west},
generate anchors={mid 1/2:.3333, mid 2/2:.6667}{north}{south},
generate anchors={out 1/2:.3333, out 2/2:.6667}{north east}{south east},
}
]
\node[draw, my anchors, anchor = mid 1/2] (a) at (0,0) {A};
\node[draw, my anchors, anchor = mid 2/2] (b) at (1,0) {B};
\draw[my anchors] (a.out 1/2) -- (b.in 2/2);
\end{tikzpicture}
\end{document}
输出
答案2
注意@Qrrbrbirlbel 的两个答案中的任何一个这里和这里比这个 hack 好多了……
我把这个答案留在这里作为如何计算边界锚点的练习。
如果节点是正方形(或具有固定比例),则可以使用边框锚点。在这种情况下,右侧 2/3 高度处的点与水平面成 198.435° 角,因此:
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}
\node[draw] at (1,0) (A) {A};
\coordinate (connection) at ($(A.north west)!0.66!(A.south west)$);
\path[draw] (0,0) -- (connection);
% if the node is a square, use the border anchor:
% atan( (1/6)/(1/2) \approx 18.435
\draw (2,0) -- ++(1,0) node[draw, anchor=198.435]{A};
\end{tikzpicture}
\end{document}
如果节点形状不固定,那就复杂得多(我不知道节点大小是否可用于边界角度计算)。
这很简单,但为了完整性:
如果不知道节点文本大小,我发现可以从锚点内部访问节点的框;以下(黑客?)似乎有效,尽管看起来锚点的定位仍然存在一些不准确之处:
\documentclass[border=10pt]{standalone}
\usepackage[T1]{fontenc}
\usepackage{tikz}
\usetikzlibrary{calc}
\newcommand\anchorat[1]{%
% this will fail is "minimum width" or "minimum height" is in force
\fpeval{180+atand(
((#1)-0.5)*(\ht\pgfnodeparttextbox+\dp\pgfnodeparttextbox+
2*\pgfkeysvalueof{/pgf/inner ysep}+2*\pgfkeysvalueof{/pgf/outer ysep})/
(0.5*(\wd\pgfnodeparttextbox+
2*\pgfkeysvalueof{/pgf/inner xsep}+2*\pgfkeysvalueof{/pgf/outer xsep}))
)}
}
\newcommand{\markpoint}[2][red]{%
\draw [#1] #2 --++(-.1,-.1) -- ++(.2,.2) ++(-.2,0) -- ++(.2,-.2);
}
\begin{document}
\begin{tikzpicture}[every node/.append style={draw=gray, align=center,
line width=1mm, inner sep=1mm}]
\draw (0,0) -- ++(1,0)
node[anchor=\anchorat{1/3}] (A) {A};
\draw (0,-2) -- ++(1,0)
node[anchor=\anchorat{2/3}] (B) {Alfa Beta Delta\\ Gamma \\ Epsilon\vrule width 1pt height 3pt depth 8pt };
\markpoint{($(A.north west)!{1/3}!(A.south west)$)}
\markpoint{($(B.north west)!{2/3}!(B.south west)$)}
\markpoint[blue]{($(B.north west)!{0}!(B.south west)$)}
\markpoint[blue]{($(B.north west)!{1}!(B.south west)$)}
\markpoint[green]{(B.text)}
\markpoint[green]{(B.center)}
\end{tikzpicture}
\end{document}
\anchorat
改变宏来指定边界中的任何点应该相当容易......
我跟着rectangle
定义形状大小的代码,我几乎确信这里的公式是正确的(至少,它们适用于 0.0、0.5、1.0)。不过,这里有一个明显的错误(虽然很小)。仍在调查中。
答案3
inout rectangle
这是一个支持多个输入和输出锚点的形状和随之而来的直线。
目前,最多仅支持 10 个连接。(TikZ 的电路形状最多允许 1024 个,还可以动态添加所需的锚点定义,但我没有实现。)10
如果您需要更多,则需要自行调整。
需要考虑的几件事:
请求输入或输出锚点数超过指定锚点数只会引发警告,而不会引发错误。它成功返回一个点,但在形状之外。
PGF 在小直线的末端增加了一半的线宽(在第一个例子中几乎看不出来,而在第二个例子中由于注释而被隐藏)。
您可以使用
/tikz/inout rectangle/every in out
样式来更改所有这些行的输出。
此外,对于每一行,都会有选择地尝试以下样式:/tikz/inout rectangle/every in
和/tikz/inout rectangle/every out
允许输入和输出线使用不同的样式,以及/tikz/inout rectangle/in 1
,,/tikz/inout rectangle/in 2
… 和 out 相同,可以单独定制每一行。
基本上是
\pgscope\begingroup … \tikz@finish\endpgfscope
TikZ\path …;
。实际上使用这样的可能更好,\path
但我没有注意到此示例中有任何缺点。out 2/.style={Circle[open, length=+0pt +3, sep=+0pt +-.5]}-
显然,实现否定输出的用户界面很糟糕。如果需要,我们可以实现类似于 TikZ 电路库的功能。使用未记录的方法来提供速度(超过)和便利性(超过没有自带计数器的循环)
\pgfmathloop … \repeatpgfmathloop
之间的良好折衷。\foreach
代码
\documentclass[tikz]{standalone}
\usetikzlibrary{arrows.meta}
\makeatletter
\tikzset{% this is explicitly a TikZ shape
inout rectangle/in length/.initial=1cm,
inout rectangle/out length/.initial=1cm,
inout rectangle/every in out/.style=draw,
inout rectangle/inputs/.initial=3,
inout rectangle/outputs/.initial=3,
inout rectangle/.code=\tikzset{shape=inout rectangle,inout rectangle/.cd,#1}}
\def\pgf@sh@inoutrectangle@test#1#2#3{\ifnum#1>#2\relax\pgfwarning{#3put #1\space not defined for this node.}\fi}
\pgfmathloop
\expandafter\edef\csname pgf@anchor@inout rectangle@input \pgfmathcounter'\endcsname{%
\noexpand\pgf@sh@inoutrectangle@test{\pgfmathcounter}{\noexpand\inputs}{In}%
\noexpand\pgfpointlineattime{\pgfmathcounter/(\noexpand\inputs+1)}
{\noexpand\pgf@sh@reanchor{inout rectangle}{north west}}
{\noexpand\pgf@sh@reanchor{inout rectangle}{south west}}}%
\expandafter\edef\csname pgf@anchor@inout rectangle@input \pgfmathcounter\endcsname{%
\noexpand\pgf@sh@inoutrectangle@test{\pgfmathcounter}{\noexpand\inputs}{In}%
\noexpand\pgfpointadd{\noexpand\pgf@sh@reanchor{inout rectangle}{input \pgfmathcounter'}}{%
\noexpand\inout\pgf@x=-\pgf@y \pgf@y=0pt }}%
\expandafter\edef\csname pgf@anchor@inout rectangle@output \pgfmathcounter'\endcsname{%
\noexpand\pgf@sh@inoutrectangle@test{\pgfmathcounter}{\noexpand\outputs}{Out}%
\noexpand\pgfpointlineattime{\pgfmathcounter/(\noexpand\outputs+1)}
{\noexpand\pgf@sh@reanchor{inout rectangle}{north east}}
{\noexpand\pgf@sh@reanchor{inout rectangle}{south east}}}%
\expandafter\edef\csname pgf@anchor@inout rectangle@output \pgfmathcounter\endcsname{%
\noexpand\pgf@sh@inoutrectangle@test{\pgfmathcounter}{\noexpand\outputs}{Out}%
\noexpand\pgfpointadd{\noexpand\pgf@sh@reanchor{inout rectangle}{output \pgfmathcounter'}}{%
\noexpand\inout\pgf@x=\pgf@x \pgf@y=0pt }}%
\ifnum\pgfmathcounter<10\relax % max number of anchors: 10
\repeatpgfmathloop
\pgfdeclareshape{inout rectangle}{%
\savedanchor\inout{% it's not a real anchor, it just saves two lengths in one macro
\pgfpoint{\pgfkeysvalueof{/tikz/inout rectangle/in length}}%
{\pgfkeysvalueof{/tikz/inout rectangle/out length}}}%
\savedmacro\inputs {\pgfmathtruncatemacro\inputs {\pgfkeysvalueof{/tikz/inout rectangle/inputs}}}%
\savedmacro\outputs{\pgfmathtruncatemacro\outputs{\pgfkeysvalueof{/tikz/inout rectangle/outputs}}}%
\inheritsavedanchors[from=rectangle]\inheritanchor[from=rectangle]{base}%
\inheritanchor[from=rectangle]{center}\inheritanchor[from=rectangle]{mid}
\inheritanchor[from=rectangle]{north}\inheritanchor[from=rectangle]{south}%
\inheritanchor[from=rectangle]{west}\inheritanchor[from=rectangle]{east}%
\inheritanchor[from=rectangle]{mid west}\inheritanchor[from=rectangle]{base west}%
\inheritanchor[from=rectangle]{north west}\inheritanchor[from=rectangle]{south west}%
\inheritanchor[from=rectangle]{mid east}\inheritanchor[from=rectangle]{base east}%
\inheritanchor[from=rectangle]{north east}\inheritanchor[from=rectangle]{south east}%
\inheritanchorborder[from=rectangle]\inheritbackgroundpath[from=rectangle]%
\behindbackgroundpath{% the lines are drawn behind the normal rectangle
\ifnum\inputs>0
\pgfmathloop
\pgfscope\begingroup
\tikzset{
inout rectangle/every in out, inout rectangle/every in/.try,
inout rectangle/in \pgfmathcounter/.try}%
\pgfpathmoveto{\pgf@sh@reanchor{inout rectangle}{input \pgfmathcounter'}}%
\pgfpathlineto{\pgf@sh@reanchor{inout rectangle}{input \pgfmathcounter}}%
\tikz@finish\endpgfscope
\ifnum\pgfmathcounter<\inputs\relax
\repeatpgfmathloop
\fi
\ifnum\outputs>0
\pgfmathloop
\pgfscope\begingroup
\tikzset{
inout rectangle/every in out, inout rectangle/every out/.try,
inout rectangle/out \pgfmathcounter/.try}%
\pgfpathmoveto{\pgf@sh@reanchor{inout rectangle}{output \pgfmathcounter'}}%
\pgfpathlineto{\pgf@sh@reanchor{inout rectangle}{output \pgfmathcounter}}%
\tikz@finish\endpgfscope
\ifnum\pgfmathcounter<\outputs\relax
\repeatpgfmathloop
\fi
}%
}
\makeatother
\begin{document}
\begin{tikzpicture}
\node[
draw, inout rectangle={inputs=2, in 1/.style=path only, outputs=0},
anchor=input 2'] (A) {A};
\end{tikzpicture}
\begin{tikzpicture}[
shape example/.style={
color=black!30, draw, fill=yellow!30,
line width=+.5cm, inner xsep=+2.5cm, inner ysep=0.5cm}]
\node[
shape example,
inout rectangle={
inputs=3,
outputs=4,
every in out/.append style={line width/.expanded=\the\dimexpr.5\pgflinewidth},
out 2/.style={Circle[open, length=+0pt +3, sep=+0pt +-.5]}-
}] (s) {Inout Rectangle\vrule width 1pt height 2cm};
\foreach \anchor/\placement in
{north west/above left, north/above, north east/above right,
south west/below left, south/below, south east/below right,
input 1/left, input 2/left, input 3/left,
input 1'/right, input 2'/right, input 3'/right,
output 1/right, output 2/right, output 3/right, output 4/right,
output 1'/left, output 2'/left, output 3'/left, output 4'/left}
\draw[shift=(s.\anchor)] plot[mark=x] coordinates{(0,0)}
node[\placement] {\scriptsize\texttt{(s.\anchor)}};
\end{tikzpicture}
\end{document}