Continuing http://tex.stackexchange.com/a/111674 and TIKZ - automatic bridge if two paths cross, we would like to draw Bézier curves in such a way that whenever they cross, one curve forms a bridge above the other curve, and the lower curve fades out (from black to white) when approaching the bridge and fades in from white to black right after exiting the bridge). Has anyone, perhaps, already done the job? Here are some bits of it, mostly stolen from TeX.SE and Github:
\documentclass{article}
\pagestyle{empty}
\usepackage{tikz}
\usetikzlibrary{calc,positioning,bbox,intersections,angles,fadings}%%% get pgflibrarybbox.code.tex from https://github.com/pgf-tikz/pgf/issues/856#issuecomment-632925356 so as to show arrow tips
\newcommand{\bridgeRadius}{.5ex}
\begin{document}
\noindent Bridge:
\begin{center}%
\begin{tikzpicture}[atomicNode/.style={rectangle,draw,minimum width=5em, minimum height=3ex}]
\node[atomicNode] (SOne) {\(S_1\)};
\coordinate (SOneZOneOut) at ($(SOne.north west)!.8!(SOne.north east)$);
\coordinate (SOneZTwoIn) at ($(SOne.south west)!.8!(SOne.south east)$);
\node[atomicNode,right=10em of SOne] (STwo) {\(S_2\)};
\coordinate (STwoZTwoOut) at ($(STwo.north west)!.2!(STwo.north east)$);
\coordinate (STwoZOneIn) at ($(STwo.south west)!.2!(STwo.south east)$);
\begin{scope}[bezier bounding box=true]
\path[name path=pathBezierZTwo,-latex] (SOneZTwoIn) .. controls ($(SOneZTwoIn)-(0,3ex)$) and ($(STwoZTwoOut)+(0,3ex)$) .. (STwoZTwoOut);
\path[name path=pathBezierZOne,-latex] (STwoZOneIn) .. controls ($(STwoZOneIn)-(0,3ex)$) and ($(SOneZOneOut)+(0,3ex)$) .. (SOneZOneOut);
\path[name intersections={of=pathBezierZTwo and pathBezierZOne,by=inter}];
\path let \p1=(SOneZTwoIn), \p2=($(SOneZTwoIn)-(0,3ex)$), \p{dir}=($(\p2)-(inter)$), \n{len}={\bridgeRadius/veclen(\p{dir})}, \p{scaleddir}=($\n{len}*(\p{dir})$) in coordinate (startHalfCircleOnZTwo) at ($(inter)+(\p{scaleddir})$);
\path let \p1=(STwoZTwoOut), \p2=($(STwoZTwoOut)+(0,3ex)$), \p{dir}=($(\p2)-(inter)$), \n{len}={\bridgeRadius/veclen(\p{dir})}, \p{scaleddir}=($\n{len}*(\p{dir})$) in coordinate (endHalfCircleOnZTwo) at ($(inter)+(\p{scaleddir})$);
\path let \p1=(STwoZOneIn), \p2=($(STwoZOneIn)-(0,3ex)$), \p{dir}=($(\p2)-(inter)$), \n{len}={\bridgeRadius/veclen(\p{dir})}, \p{scaleddir}=($\n{len}*(\p{dir})$) in coordinate (startHalfCircleOnZOne) at ($(inter)+(\p{scaleddir})$);
\path let \p1=(SOneZOneOut), \p2=($(SOneZOneOut)+(0,3ex)$), \p{dir}=($(\p2)-(inter)$), \n{len}={\bridgeRadius/veclen(\p{dir})}, \p{scaleddir}=($\n{len}*(\p{dir})$) in coordinate (endHalfCircleOnZOne) at ($(inter)+(\p{scaleddir})$);
\draw[-latex] (STwoZOneIn) .. controls ($(STwoZOneIn)-(0,3ex)$) .. (startHalfCircleOnZOne) -- (endHalfCircleOnZOne) .. controls ($(SOneZOneOut)+(0,3ex)$) .. node[above,inner sep=.22ex,pos=.59](ZOneNorthText){\(z_1\)} (SOneZOneOut);
\draw (SOneZTwoIn) .. controls ($(SOneZTwoIn)-(0,3ex)$) .. (startHalfCircleOnZTwo);
\pic [draw, angle radius=\bridgeRadius] {angle=endHalfCircleOnZTwo--inter--startHalfCircleOnZTwo};
\draw[-latex] (endHalfCircleOnZTwo) .. controls ($(STwoZTwoOut)+(0,3ex)$) .. node[above,inner sep=.22ex,pos=.59](ZOneNorthText){\(z_2\)} (STwoZTwoOut);
\end{scope}
\draw (current bounding box.north west) rectangle (current bounding box.south east);
\end{tikzpicture}%
\end{center}
Fading:
\begin{center}%
\makeatletter
\newif\iftikz@shading@path
\tikzset{
% There are three circumstances in which the fading sep is needed:
% 1. Arrows which do not update the bounding box (which is most of them).
% 2. Line caps/joins and mitres that extend outside the natural bounding
% box of the path (these are not calculated by PGF).
% 3. Other reasons that haven't been anticipated.
shading xsep/.store in=\tikz@pathshadingxsep,
shading ysep/.store in=\tikz@pathshadingysep,
shading sep/.style={shading xsep=#1, shading ysep=#1},
shading sep=0.0cm,
}
\def\tikz@shadepath#1{%
% \tikz@addmode installs the `modes' (e.g., fill, draw, shade)
% to be applied to the path. It isn't usualy for doing more
% changes to the path's construction.
\iftikz@shading@path%
\else%
\tikz@shading@pathtrue%
% Get the current path.
\pgfgetpath\tikz@currentshadingpath%
% Get the shading sep without setting any other keys.
\begingroup%
\pgfsys@beginscope% <- may not be necessary
\tikzset{#1}%
\xdef\tikz@tmp{\noexpand\def\noexpand\tikz@pathshadingxsep{\tikz@pathshadingxsep}%
\noexpand\def\noexpand\tikz@pathshadingysep{\tikz@pathshadingysep}}%
\pgfsys@endscope%
\endgroup
\tikz@tmp%
% Get the boudning box of the current path size including the shading sep
\pgfextract@process\pgf@shadingpath@southwest{\pgfpointadd{\pgfqpoint{\pgf@pathminx}{\pgf@pathminy}}%
{\pgfpoint{-\tikz@pathshadingxsep}{-\tikz@pathshadingysep}}}%%
\pgfextract@process\pgf@shadingpath@northeast{\pgfpointadd{\pgfqpoint{\pgf@pathmaxx}{\pgf@pathmaxy}}%
{\pgfpoint{\tikz@pathshadingxsep}{\tikz@pathshadingysep}}}%
% Clear the path
\pgfsetpath\pgfutil@empty%
% Save the current drawing mode and options.
\let\tikz@options@saved=\tikz@options%
\let\tikz@mode@saved=\tikz@mode%
\let\tikz@options=\pgfutil@empty%
\let\tikz@mode=\pgfutil@empty%
% \tikz@options are processed later on.
\tikz@addoption{%
\pgfinterruptpath%
\pgfinterruptpicture%
\begin{tikzfadingfrompicture}[name=.]
\pgfscope%
\tikzset{shade path/.style=}% Make absolutely sure shade path is not inherited.
\path \pgfextra{%
% Set the softpath. Any transformations,draw=none} in #1 will have no effect.
% This will *not* update the bounding box...
\pgfsetpath\tikz@currentshadingpath%
% ...so it is done manually.
\pgf@shadingpath@southwest
\expandafter\pgf@protocolsizes{\the\pgf@x}{\the\pgf@y}%
\pgf@shadingpath@northeast%
\expandafter\pgf@protocolsizes{\the\pgf@x}{\the\pgf@y}%
% Install the drawing modes and options.
\let\tikz@options=\tikz@options@saved%
\let\tikz@mode=\tikz@mode@saved%
};
% Now get the bounding box of the picture.
\xdef\pgf@shadingboundingbox@southwest{\noexpand\pgfqpoint{\the\pgf@picminx}{\the\pgf@picminy}}%
\xdef\pgf@shadingboundingbox@northeast{\noexpand\pgfqpoint{\the\pgf@picmaxx}{\the\pgf@picmaxy}}%
\endpgfscope
\end{tikzfadingfrompicture}%
\endpgfinterruptpicture%
\endpgfinterruptpath%
% Install a rectangle that covers the shaded/faded path picture.
\pgftransformreset%
\pgfpathrectanglecorners{\pgf@shadingboundingbox@southwest}{\pgf@shadingboundingbox@northeast}%
%
% Reset all modes.
\let\tikz@path@picture=\pgfutil@empty%
\tikz@mode@fillfalse%
\tikz@mode@drawfalse%
% \tikz@mode@tipsfalse% <- To have successful compilation with pgf-tikz v3.0.1a
\tikz@mode@doublefalse%
\tikz@mode@clipfalse%
\tikz@mode@boundaryfalse%
\tikz@mode@fade@pathfalse%
\tikz@mode@fade@scopefalse%
% Now install shading options.
\tikzset{#1}%
\tikz@mode%
% Make the fading happen.
\def\tikz@path@fading{.}%
\tikz@mode@fade@pathtrue%
\tikz@fade@adjustfalse%
% Shift the fading to the mid point of the rectangle
\pgfpointscale{0.5}{\pgfpointadd{\pgf@shadingboundingbox@southwest}{\pgf@shadingboundingbox@northeast}}%
\edef\tikz@fade@transform{shift={(\the\pgf@x,\the\pgf@y)}}%
\pgfsetfading{\tikz@path@fading}{\tikz@do@fade@transform}%
\tikz@mode@fade@pathfalse%
}%
\fi%
}
\tikzset{
shade path/.code={%
\tikz@addmode{\tikz@shadepath{#1}}%
}
}
\makeatother % <- To close the \makeatletter call
\begin{tikzpicture}[atomicNode/.style={rectangle,draw,minimum width=2em,minimum height=2ex}]
\coordinate (center);
\node[atomicNode,left=of center] (A) {};
\node[atomicNode,right=of center] (B) {};
\node[atomicNode,above=of center] (C) {};
\node[atomicNode,below=of center] (D) {};
\coordinate (Aout) at (A.east);
\coordinate (Bin) at (B.west);
\coordinate (Cout) at (C.south);
\coordinate (Din) at (D.north);
\draw[-] (Aout) -- ($(center)-(.4999ex,0)$);
\draw[draw=transparent!0,-,shade path={shading xsep=.01ex, left color=black, right color=white}] ($(center)-(.5001ex,0)$) -- ($(center)-(.2pt,0)$);
\path[draw=transparent!0,-,shade path={shading xsep=.01ex, left color=white, right color=black}] ($(center)+(.2pt,0)$) -- ($(center)+(.5001ex,0)$);
\draw[-latex] ($(center)+(.4999ex,0)$) -- (Bin);
\draw[-latex] (Cout) -- (Din);
\end{tikzpicture}
\end{center}
\end{document}
Running pdflatex
on it gives this:
Here is the screenshot of the crossing under 415,8% magnification of the PDF viewed in evince
:
The Bézier curve bridge suffers from various problems itself:
The intersection of Bézier curves is computed on the basis of one pair of curves, where as in reality a slightly different pair of curves is drawn. We are simply extremely lucky to have these two curves very symmetrical: I wouldn't know how to draw another pair of curves with a bridge in general.
The "lower" z₁ Bézier curve is drawn with a straight line segment in the middle (so that it is drawn symmetrically to the "upper" z₂ Bézier curve), whereas in general the lower curve should be "curved". In our case, the difference is unnoticeable, but in general, it can be very well noticeable.
For the fading drawing, the "lower" line does not only fade out and then fade in, but it also gets thinner. However, the thickness of the fading part should remain the same.
I don't know how to solve these issues separately, let alone together. Independent solutions for these issues are welcome.
Moreover, if anyone would have a macro such as, say, \drawBezierCurvesWithBridgesFadingAndAnnotations{curvename1}{labelnodespecification1}{label1}{curvename2}{labelnodespecification2}{label2}
, which gets named paths, the label placement options, and the labels, and draws all this with a bridge at the crossing and makes the lower curve fade when disappearing below the bridge, please feel free... (Btw, I'd like to have bridges ∩ rather than tunnels ∪ !)