根据大小移动 TikZ 节点

根据大小移动 TikZ 节点

以下 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}

输出

在此处输入图片描述

我再说一遍:下面的矩形也具有序言中定义的in 1/2和可用的锚点。in 2/2
在此处输入图片描述

在此处输入图片描述

答案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\endpgfscopeTikZ \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}

输出

在此处输入图片描述

在此处输入图片描述

相关内容