使用控制点计算 TikZ 路径中的标签宽度

使用控制点计算 TikZ 路径中的标签宽度

我有一个简单的自定义 TikZ 路径样式,可以简化为以下样式:

\tikzset{custom/.style={
    to path={
        (\tikztostart) ..
        controls ($(\tikztostart)!.3!(\tikztotarget)$)
            and ($(\tikztostart)!.7!(\tikztotarget)$)
        .. (\tikztotarget)\tikztonodes}
}}

(在这个例子中,控制点基本上是无用的,因为没有曲线,但这代表了我的实际风格存在的问题,它更为复杂。)

对于标签较短的节点之间的路径,这没有问题。但是,对于标签长度超过起始节点中心和目标节点中心之间距离 30% 的节点,存在渲染问题,因为控制点位于节点标签内,因此箭头指向错误的方向。

例如代码:

\documentclass{article}

\usepackage{tikz-cd}
\usetikzlibrary{calc}

\begin{document}

\tikzset{custom/.style={
    to path={
        (\tikztostart) ..
        controls ($(\tikztostart)!.3!(\tikztotarget)$)
            and ($(\tikztostart)!.7!(\tikztotarget)$)
        .. (\tikztotarget)\tikztonodes}
}}

\[\begin{tikzcd}
    X & X
    \arrow[custom, from=1-1, to=1-2]
\end{tikzcd}\]

\[\begin{tikzcd}
    XXXXXXXXX & XXXXXXXXX
    \arrow[custom, from=1-1, to=1-2]
\end{tikzcd}\]

\end{document}

生成:

带有长标签的节点的箭头方向不正确

请注意,第一个箭头没有问题,但第二个箭头的箭头指向错误的方向。

我在这里计算路径的思维模型显然是错误的。我希望\tikztostart参考起始节点的中心,并\tikztoend参考终止节点的中心。(当然,如果我们使控制点垂直偏移,引入曲线,情况似乎就是这样。)然后(\tikztostart)!.3!(\tikztotarget)表示从起始节点中心到终止节点中心距离的 30% 的点,对于 也是如此(\tikztostart)!.7!(\tikztotarget)。然后应该修剪路径,使其位于起始节点和终止节点的边界框之外,并在路径的末端绘制一个箭头。

然而,这显然不是正在发生的事情,否则箭头会指向正确的方向:相反,路径似乎始于路径首次可见的点(即起始节点边界框​​的右侧),并结束于路径最后可见的点(即目标节点边界框​​的左侧)。正如我所料,控制点似乎基于起始节点和结束节点的中心。但我不明白这种行为,因为和\tikztostart对于\tikztoend(1) 起始和结束坐标以及 (2) 控制点的表现似乎不同。即对于 (1) 它们基于裁剪后的路径以不与标签边界框重叠,而对于 (2) 它们基于节点中心。

因此我有两个问题:

  1. 如上所述,我的心智模型有什么问题?
  2. 如何修复路径样式以使其按照我的期望运行(即使其符合我上面的心理模型)?(特别是,这意味着箭头始终指向正确的方向。)

答案1

代码

\documentclass[varwidth]{standalone}
%\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{cd, intersections, spath3, bending}% always bending with curves
\usetikzlibrary{decorations.pathreplacing, ext.arrows}% only for this document
\tikzset{
  debug curve/.style={
    postaction=decorate,
    decoration={name=show path construction, curveto code={
      \path[thin,-{Centered Circle[open]},line to]
      (\tikzinputsegmentfirst) edge (\tikzinputsegmentsupporta)
      (\tikzinputsegmentlast ) edge (\tikzinputsegmentsupportb);}}}}
\begin{document}
In the following we will assume that \verb|\tikztostart| and \verb|\tikztotarget|
will hold names of rectangular nodes.
This usually means the shape \verb|rectangle| but in the case of TikZ-CD,
the shape \verb|asymmetrical rectangle| is actually used.
It will not change our approach, though.

This means, all this will fail if a normal coordinate like \verb|(0, 0)| or
an anchor specification like \verb|(Foo.north east)| is used.

In a TikZ-CD where the nodes usually have no drawn border and
the \verb|outer sep|s should have been set to zero, there's almost no difference
in whether we find a point \emph{on} the line of the node's path or
\emph{on the border} of a node but in the examples below
I will use exaggerated node and path styles.

\tikzset{
  exaggerated node style/.style={% this is “shape example”
    color=black!30, draw, fill=yellow!30,
    line width=+.15cm, inner xsep=+.7cm, inner ysep=+.5cm},
  exaggerated path style/.style={black, line width=+.1cm}}
\bigskip

We will use a \verb|to path| that is provided
by the always loaded \verb|topaths| library.
Let's draw an \verb|s bend|.
\makeatletter
\tikzset{
  s bend/.style={relative, out={-(#1)}, in={180-(#1)}, distance=3cm},
  s bend/.default=\tikz@to@bend,
}% support for “bend angle”
\makeatother
\tikzcdset{
  exaggerated/.style={
    cells={nodes=exaggerated node style},
    arrows={exaggerated path style, debug curve},
    column sep=1cm, math mode=false}}

Let's look at our curve between two nodes in a TikZ-Cd environment:
\[
\begin{tikzcd}[exaggerated]
Start \drar[s bend, gray, "Whee!"  sloped, start anchor=center, end anchor=center]
      \drar[s bend,       "Whoah!" sloped] & \\ & End
\end{tikzcd}
\]

We want the gray curve but not the part inside the node.
The black one looks totally different!
\bigskip

Let's find the intersection between the curve and the nodes
and use \verb|spath3| to trim the curve to.
For this, we will have every node save its path.
\textbf{However, this won't work with a matrix}
which is also used internally by TikZ-CD.
\makeatletter
\tikzset{
  save my path/.style={spath/save global=s bend \tikz@fig@name},
  s bend spath/.default=\tikz@to@bend,
  s bend spath/.style={
    to path={
      \pgfextra
      \pgfinterruptpath
        \expanded{% no clear tonodes?
          \noexpand\path (\tikztostart.center) to[s bend={#1}]
                         (\tikztotarget.center) [spath/save global=s bend curve];}%
      \endpgfinterruptpath
      \endpgfextra
      [spath/.cd,
        split at intersections with/.expanded={s bend curve}{s bend \tikztostart },
        split at intersections with/.expanded={s bend curve}{s bend \tikztotarget},
        remove components={s bend curve}{1, 3},
        use=s bend curve,
      ] \tikztonodes
    }
  }
}
\makeatother

Note, how the black line starts at the middle of the path of the nodes.
I don't like it, this totally ignores any \verb|outer sep| settings.
(Again, for nodes whose border aren't stroked or are very thin
this is barely noticeable.)
\[
\begin{tikzpicture}[sloped]
\path[nodes={exaggerated node style, save my path}]
  node (Start)            {Start}
  node (End)   at (3, -2) {End};
\path[-tikzcd to, exaggerated path style, every edge/.append style=debug curve]
  (Start.center) edge[s bend, gray]                     (End.center)
  (Start)        edge[s bend spath] node[above] {Whee!} (End);
\end{tikzpicture}
\]
But we can't use this with matrices anyway \dots\bigskip

However, as we only use rectangular nodes, we can reconstruct the path easily.
Once again, I will assume that the nodes are not transformed
and that there's no transformation set when we draw the curve.

\makeatletter
\tikzset{
  clear tonodes/.style={execute at begin to=\let\tikztonodes\pgfutil@empty},
  s bend spath matrix/.default=\tikz@to@bend,
  s bend spath matrix/.style={
    to path={
      \pgfextra
      \pgfinterruptpath
        \path (\tikztostart.south west) rectangle (\tikztostart.north east)
              (\tikztotarget.south west) rectangle (\tikztotarget.north east)
              [spath/save global=s bend rect];
        \expanded{%
          \noexpand\path[clear tonodes] (\tikztostart.center) to[s bend={#1}]
            (\tikztotarget.center) [spath/save global=s bend curve];}%
      \endpgfinterruptpath
      \endpgfextra
      [spath/.cd,
        split at intersections with/.expanded={s bend curve}{s bend rect},
                  remove components={s bend curve}{1, 3},
                  use=s bend curve
      ] \tikztonodes
    }
  }
}
\makeatother

\[
\begin{tikzcd}[exaggerated]
Start \drar[s bend, gray, start anchor=center, end anchor=center]
      \drar[s bend spath matrix, "Whee!" sloped] & \\ & End
\end{tikzcd}
\]
\end{document}

输出

在此处输入图片描述

在此处输入图片描述

相关内容