我想在 tikz-cd 交换图中绘制一个穿过另一个箭头的箭头。我可以从文档中按如下方式执行此操作:
\documentclass{article}
\usepackage{tikz-cd}
\begin{document}
\[
\begin{tikzcd}[ampersand replacement=\&,column sep=small]
GM \arrow[pos=0.75]{rr}{\models_{M}}
\arrow[two heads,swap]{dr}{e_{M}}
\arrow[swap]{dd}{Gf}
\& \&
R^{FM}
\arrow{dd}{R^{Ff}} \\
\& T M
\arrow[tail,swap]{ru}{m_{M}}
\arrow[swap,pos=0.75]{dd}{T f}
\& \\
GN \arrow[pos=0.75,crossing over]{rr}{\models_{N}}
\arrow[two heads,swap]{dr}{e_{N}}
\& \& R^{FN} \\
\& T N
\arrow[tail,swap]{ru}{m_{N}} \&
\end{tikzcd}
\]
\end{document}
但是,我希望箭头$Tf$
越过箭头$\models_{N}$
。(即上下交叉颠倒)。但是,由于应该绘制在另一个箭头上方的箭头在另一个箭头之前绘制,因此“交叉”选项不起作用。
我尝试使用节点作为图表中的条目,以便稍后绘制箭头(从一个节点到另一个节点),但这会给我一个编译错误。我总是可以返回到普通的 tikz,但我想其他使用 tikz-cd 的人可能会发现了解如何使用节点或延迟绘制箭头也很有帮助。有什么提示吗?
答案1
像这样?
虽然我必须承认我还没有通用的解决方案,但对于这个特殊情况,效果是通过从 TN 到 TM 而不是从 TM 到 TN 绘制箭头来实现的。这样箭头就画出来了后水平的。当然我以前总是leftarrow
把箭头弄对。
\documentclass{article}
\usepackage{tikz-cd}
\begin{document}
\[
\begin{tikzcd}[ampersand replacement=\&,column sep=small]
GM \arrow[pos=0.75]{rr}{\models_{M}}
\arrow[two heads,swap]{dr}{e_{M}}
\arrow[swap]{dd}{Gf}
\& \&
R^{FM}
\arrow{dd}{R^{Ff}} \\
\& T M
\arrow[tail,swap]{ru}{m_{M}}
\& \\
GN \arrow[pos=0.75,crossing over]{rr}{\models_{N}}
\arrow[two heads,swap]{dr}{e_{N}}
\& \& R^{FN} \\
\& T N
\arrow[leftarrow,pos=0.25,crossing over]{uu}{T f} % <<----- HERE
\arrow[tail,swap]{ru}{m_{N}} \&
\end{tikzcd}
\]
\end{document}
更新:更通用的解决方案
上述解决方案是特别指定并且仅适用于此特定示例。一般解决方案允许在已排版的交换图的任何两个节点之间绘制任何附加箭头。
想法如下。既然内部tikzcd
使用matrix of nodes
排版图表的每个节点,那么为什么不能将这些节点用作图表后绘制的路径的一部分呢?
这个想法虽然合理,但是也存在一些问题:
- 图中每个节点的名称是什么?是否可以使用
[(name)]
tikz 中通常允许的语法matrix of nodes
?答案:不可能,这会破坏\arrow
宏的代码,该代码依赖于节点的自动生成名称。 - 那么有自动生成的名称吗?这些名称是什么?答案:它们都以 开头
\tikzmatrixname
并带有后缀-row-column
。例如,此图的第一个节点(带有标签 GM)将是\tikzmatrixname-1-1
,中心节点(TM)是\tikzmatrixname-2-2
,依此类推。 因此,可以添加
\draw (\tikzmatrixname-1-1) to[bend left] (\tikzmatrixname-2-2)
;例如,在矩阵完成后?即:例如:\begin{tikzcd}[ampersand replacement=\&,column sep=small] GM \arrow[pos=0.75]{rr}{\models_{M}} [...] \arrow[tail,swap]{ru}{m_{N}} \& % After all nodes are in the matris, draw some extra arrows \draw[bend left] (\tikzmatrixname-1-1) to[bend left] (\tikzmatrixname-2-2); \end{tikzcd}
答案:很遗憾,答案是否定的,这是不可能的,因为
tikcd
处理图表的方式。在tikzcd
环境内部,令人惊讶的是,tikz
无法发出任何命令。事实上,所有tikz
命令都是未定义的,因为tikzcd
环境不是环境tikzpicture
。这解释了为什么您不能使用\node
或\draw
任何其他命令。tikzcd
每次使用命令\arrow
或其他等效命令时,都会计算适当的\path
命令,但不是“执行”它,而是将其存储在一个列表中,其中包含所有其他\arrow
命令产生的路径。在环境的末尾,会创建tikzcd
一个包含,在该图片中,最终执行命令列表,绘制所有箭头。tikzpicture
matrix of nodes
\path
所以,我们能做些什么?
tikzcd
如果能提供一种\drawAtTheEnd
命令,允许以与路径列表相同的方式存储任何 tikz 绘图命令,并在环境末尾执行存储的命令,那就太好了tikzcd
。这样,我们可以添加其他箭头、装饰,定义节点坐标以便overlay, remember picture
从页面的其他部分访问,等等。
同时,我编写了一个 hack,允许将新路径插入到 tikzcd 使用的内部列表中。调用宏\latearrow
,它需要四个参数:
- 箭头的 tikz 选项(你可以传递颜色、
to path
spos
等 - 起始节点的名称(不带部分)
\tikzmatrixnode
。例如:若要引用 GM 节点,则将其称为1-1
,若要引用 TM 节点,则将其称为2-2
,依此类推。 - 结束节点的名称(也不包括该
\tikzmatrixnode
部分) - 箭头中标签的文字。
使用该宏,您可以首先绘制完整的交换图(除了从 TM 到 TN 的箭头),然后使用以下语法在末尾添加该箭头:
\latearrow{pos=0.75,crossing over}{2-2}{4-2}{T f}
如上所述,这是一个快速而肮脏的黑客行为,它不使用与其他箭头相同的语法,不允许标签的样式选项,我不确定它是否会破坏任何东西,但显然它是有效的。以下是代码:
\documentclass{article}
\usepackage{tikz-cd}
\makeatletter
\def\latearrow#1#2#3#4{%
\toks@\expandafter{\tikzcd@savedpaths\path[/tikz/commutative diagrams/every arrow,#1]}%
\global\edef\tikzcd@savedpaths{%
\the\toks@%
(\tikzmatrixname-#2)% \noexpand\tikzcd@sourceanchor)%
to%
node[/tikz/commutative diagrams/every label] {$#4$}
(\tikzmatrixname-#3)% \noexpand\tikzcd@targetanchor)
;}}
\makeatother
\begin{document}
\[
\begin{tikzcd}[ampersand replacement=\&,column sep=small]
GM \arrow[pos=0.75]{rr}{\models_{M}}
\arrow[two heads,swap]{dr}{e_{M}}
\arrow[swap]{dd}{Gf}
\& \&
R^{FM}
\arrow{dd}{R^{Ff}} \\
\& T M
\arrow[tail,swap]{ru}{m_{M}}
\& \\
GN \arrow[pos=0.75]{rr}{\models_{N}}
\arrow[two heads,swap]{dr}{e_{N}}
\& \& R^{FN} \\
\& T N
\arrow[tail,swap]{ru}{m_{N}} \&
\latearrow{pos=0.75,crossing over}{2-2}{4-2}{T f}
\latearrow{red, bend left}{1-1}{2-2}{x}
\end{tikzcd}
\]
\end{document}
请注意,我blue
为该箭头添加了一个选项,以证明其有效。为了好玩,我还从 GM 到 TM 添加了一条红色弯曲的“晚箭头”。结果如下:
答案2
可以通过将以下命令放在图表末尾,直接使用 tikzcd 获得最佳答案的功能:
\arrow[from=2-2, to=4-2, pos=0.75, crossing over, "T f"']
因此完整的例子如下:
\documentclass{article}
\usepackage{tikz-cd}
\begin{document}
\[
\begin{tikzcd}[ampersand replacement=\&,column sep=small]
GM \arrow[pos=0.75]{rr}{\models_{M}}
\arrow[two heads,swap]{dr}{e_{M}}
\arrow[swap]{dd}{Gf}
\& \&
R^{FM}
\arrow{dd}{R^{Ff}} \\
\& T M
\arrow[tail,swap]{ru}{m_{M}}
\& \\
GN \arrow[pos=0.75,crossing over]{rr}{\models_{N}}
\arrow[two heads,swap]{dr}{e_{N}}
\& \& R^{FN} \\
\& T N
\arrow[tail,swap]{ru}{m_{N}} \&
\arrow[from=2-2, to=4-2, pos=0.75, crossing over, "T f"']
\end{tikzcd}
\]
\end{document}
通过定义命令的解决方案\latearrow
与 JLDiaz 给出的解决方案类似,您可以定义一个命令\latearrow
。我的版本的\latearrow
设置是,tikzcd 首先按出现的顺序打印普通箭头,然后\latearrow
按出现的顺序打印 s。的语法\latearrow
与的语法完全相同\arrow
。
请注意,这可以正确处理新语法和旧语法,因此您可以使用旧语法:
\latearrow[pos=0.75, crossing over,swap]{dd}{T f}
或新语法:
\latearrow[dd,pos=0.75, crossing over,"T f"']
并且两者都有效(使这个工作对于旧语法来说增加了 8 次调用\patchcmd
和 3 次\let
调用)。
\documentclass{article}
\usepackage{etoolbox}
\usepackage{tikz-cd}
\makeatletter
\patchcmd\tikzcd@
{\global\let\tikzcd@savedpaths\pgfutil@empty}
{\global\let\tikzcd@savedpaths\pgfutil@empty
\global\let\tikzcd@atendsavedpaths\pgfutil@empty}{}{}
% For some reason this \patchcmd call fails =(
%\patchcmd\endtikzcd{\tikzcd@savedpaths}{\tikzcd@savedpaths\tikzcd@atendsavedpaths}{}{}
% So we have to copy the whole original version of \endtikzcd
\def\endtikzcd{%
\pgfmatrixendrow\egroup%
\pgfextra{\global\let\tikzcdmatrixname\tikzlastnode};%
\tikzcdset{\the\pgfmatrixcurrentrow-row diagram/.try}%
\begingroup%
\pgfkeys{% `quotes' library support
/handlers/first char syntax/the character "/.initial=\tikzcd@forward@quotes,%
/tikz/edge quotes mean={%
edge node={node [execute at begin node=\iftikzcd@mathmode$\fi,%$
execute at end node=\iftikzcd@mathmode$\fi,%$
/tikz/commutative diagrams/.cd,every label,##2]{##1}}}}%
\let\tikzcd@errmessage\errmessage% improve error messages
\def\errmessage##1{\tikzcd@errmessage{##1^^J...^^Jl.\tikzcd@lineno\space%
I think the culprit is a tikzcd arrow in cell
\tikzcd@currentrow-\tikzcd@currentcolumn}}%
\tikzcd@before@paths@hook%
\tikzcd@savedpaths\tikzcd@atendsavedpaths % I just added \tikzcd@atendsavedpaths
\endgroup%
\endtikzpicture%
\ifnum0=`{}\fi}
\let\latearrow\tikzcd@arrow % \patchcmd comes through for us here.
\let\late@@arrow\tikzcd@@arrow
\let\late@ar@new\tikzcd@ar@new
\let\late@ar@old\tikzcd@ar@old
\let\late@ar@getlabel\tikzcd@ar@getlabel
\patchcmd\latearrow{\tikzcd@savedpaths}{\tikzcd@atendsavedpaths}{}{}
\patchcmd\latearrow{\tikzcd@@arrow}{\late@@arrow}{}{}
\patchcmd\latearrow{\tikzcd@ar@old}{\late@ar@old}{}{}
\patchcmd\late@@arrow{\tikzcd@ar@old}{\late@ar@old}{}{}
\patchcmd\late@@arrow{\tikzcd@ar@new}{\late@ar@new}{}{}
\patchcmd\late@ar@old{\tikzcd@ar@getlabel}{\late@ar@getlabel}{}{}
\patchcmd\late@ar@old{\tikzcd@ar@getlabel}{\late@ar@getlabel}{}{}
\patchcmd\late@ar@old{\tikzcd@ar@new}{\late@ar@new}{}{}
\patchcmd\late@ar@getlabel{\tikzcd@ar@getlabel}{\late@ar@getlabel}{}{}
\patchcmd\late@ar@getlabel{\tikzcd@ar@getlabel}{\late@ar@getlabel}{}{}
\patchcmd\late@ar@getlabel{\tikzcd@ar@new}{\late@ar@new}{}{}
\patchcmd\late@ar@new{\tikzcd@savedpaths}{\tikzcd@atendsavedpaths}{}{}
\def\tikzcd@atendsavedpaths{}
\makeatother
\begin{document}
\[
\begin{tikzcd}[ampersand replacement=\&,column sep=small]
GM \arrow[pos=0.75]{rr}{\models_{M}}
\arrow[two heads,swap]{dr}{e_{M}}
\arrow[swap]{dd}{Gf}
\& \&
R^{FM}
\arrow{dd}{R^{Ff}} \\
\& T M\latearrow[pos=0.75, crossing over,swap]{dd}{T f}
% Here's the \latearrow call. This arrow will go on top.
\arrow[tail,swap]{ru}{m_{M}}
\& \\
GN \arrow[pos=0.75,crossing over]{rr}{\models_{N}}
\arrow[two heads,swap]{dr}{e_{N}}
\& \& R^{FN} \\
\& T N
\arrow[tail,swap]{ru}{m_{N}} \&
\end{tikzcd}
\]
\end{document}
代码的工作原理是 1) 进行更改,\end{tikzcd}
以便将和都放入\tikzcd@savedpaths
图\tikzcd@atendsavedpaths
中,以及 2) 复制代码\arrow
但对其进行修改,以将路径存储在 中\tikzcd@atendsavedpaths
而不是 中\tikzcd@savedpaths
。因为最后它们是按照 的顺序使用的
\tikzcd@savedpaths\tikzcd@atendsavedpaths
,所以首先打印使用正常\arrow
命令创建的所有路径,然后打印使用 的路径\latearrow
,每个路径都按照它们出现的顺序打印。
由于某种原因,\patchcmd
执行步骤 1 的调用失败,因此我只是复制了整个定义\endtikzcd
并添加了我需要的一个额外命令。
我执行第 2 步,复制 中使用的所有内部命令\arrow
,然后使用一堆调用将\patchcmd
它们相互链接,并替换 savedpaths 宏。由于\arrow
语法异常复杂,因此有四个内部命令,每个命令最多需要三次替换。
显然,对 tikzcd 的更新可能会破坏此代码,但它非常简单,所以修复起来应该不难。
编辑:我昨天发布的版本有问题,感谢 Aaron Mazel 的注意。我错误地认为 savedpaths 是本地的,并且会在环境结束时范围关闭时自动清空。相反,它是全局的,并在下一个 tikzcd 环境开始时清空。修复方法是添加以下行:
\patchcmd\tikzcd@
{\global\let\tikzcd@savedpaths\pgfutil@empty}
{\global\let\tikzcd@savedpaths\pgfutil@empty
\global\let\tikzcd@atendsavedpaths\pgfutil@empty}{}{}
答案3
一种方法是将穿过下方的箭头分成两部分。以下是一些如何做到这一点的想法:
一种可能性是利用这样一个事实:在你想要断开其中一个箭头的位置恰好有一个矩阵单元:
\documentclass{article}
\usepackage{tikz-cd}
\begin{document}
\begin{tikzcd}[]
& A \arrow{dd} & \\
B \arrow[-]{r} & {} \arrow{r}{\phi} & C \\
& D &
\end{tikzcd}
\end{document}
或者,您可以使用 TikZ 的垂直坐标系(请参阅 pgf 手册第 13.3.1 节)。在下面的示例中,从 B 开始的箭头将落在 D 中,但我们通过以下方式修改实际绘制的路径to path={-- (\tikztostart -| \tikztotarget)}
(to path
关键在 pgf 手册第 14.14 节中解释)。这会将箭头的端点更改为通过 \tikztostart(在本例中为包含 B 的节点)的水平线与通过 \tikztotarget(在本例中为包含 D 的节点)的垂直线的交点。对于该箭头的第二部分,我们以类似的方式进行。
\documentclass{article}
\usepackage{tikz-cd}
\begin{tikzcd}[]
&
A \arrow{dd}
& \\
B \arrow[-,
shorten >=0.7ex,
to path={-- (\tikztostart -| \tikztotarget)}]
{rd}
&
&
C \\
&
D \arrow[shorten <=0.7ex,
to path={(\tikztostart |- \tikztotarget)
-- (\tikztotarget) \tikztonodes}]
{ru}
{\phi}
&
\end{tikzcd}
\end{document}
最后,可以使用intersections
库(pgf 手册第 13.3.2 节)对上述可能倾斜或弯曲的线条进行概括。我希望下面的例子是不言自明的。
\documentclass{article}
\usepackage{tikz-cd}
\usetikzlibrary{intersections}
\begin{tikzcd}
A \arrow[name path=above]{rd}
& B \arrow[name path=below, draw=none]{ld}
\arrow[name intersections={of=above and below},
-,
shorten >=0.7ex,
to path={-- (intersection-1)}]{}
\arrow[shorten <=0.7ex,
to path={(intersection-1) -- (\tikztotarget) \tikztonodes}]
{ld}{\phi}
\\
C
& D
\end{tikzcd}
\end{document}