How can I draw edge bundles with TikZ?
Here is a proposal that uses a decoration for the quadruple lines and the execute at begin to
option to draw separate paths.
\documentclass[tikz, border=3.14mm]{standalone}
\usetikzlibrary{arrows.meta,decorations.markings,decorations}
\def\bundlesep{2pt}% distance between parallel edges
\def\bundleX{0.8*\bundlesep}
\def\bundleY{1.8*\bundlesep}% x and y radius of ellipse
\pgfdeclaredecoration{dublines}{initial}
{%
\state{initial}[width=0pt,next state=final] {
\pgfcoordinate{dubstart1}{\pgfpoint{0pt}{-0.5*\bundlesep}}
\pgfcoordinate{dubstart2}{\pgfpoint{0pt}{0.5*\bundlesep}}
}
\state{final}[width=\pgfdecoratedpathlength]
{
\pgfmoveto{\pgfpointanchor{dubstart1}{center}}
\pgfpathlineto{\pgfpoint{\pgfdecoratedpathlength}{-0.5*\bundlesep}}
\pgfmoveto{\pgfpointanchor{dubstart2}{center}}
\pgfpathlineto{\pgfpoint{\pgfdecoratedpathlength}{0.5*\bundlesep}}
\pgfmoveto{\pgfpointdecoratedpathlast}
}
}
\pgfdeclaredecoration{quadlines}{initial}
{%
\state{initial}[width=0pt,next state=final] {
\pgfcoordinate{quadstart1}{\pgfpoint{0pt}{-1*\bundlesep}}
\pgfcoordinate{quadstart2}{\pgfpoint{0pt}{-0.33*\bundlesep}}
\pgfcoordinate{quadstart3}{\pgfpoint{0pt}{0.33*\bundlesep}}
\pgfcoordinate{quadstart4}{\pgfpoint{0pt}{1*\bundlesep}}
}
\state{final}[width=\pgfdecoratedpathlength]
{
\pgfmoveto{\pgfpointanchor{quadstart1}{center}}
\pgfpathlineto{\pgfpoint{\pgfdecoratedpathlength}{-1*\bundlesep}}
\pgfmoveto{\pgfpointanchor{quadstart2}{center}}
\pgfpathlineto{\pgfpoint{\pgfdecoratedpathlength}{-0.33*\bundlesep}}
\pgfmoveto{\pgfpointanchor{quadstart3}{center}}
\pgfpathlineto{\pgfpoint{\pgfdecoratedpathlength}{0.33*\bundlesep}}
\pgfmoveto{\pgfpointanchor{quadstart4}{center}}
\pgfpathlineto{\pgfpoint{\pgfdecoratedpathlength}{1*\bundlesep}}
\pgfmoveto{\pgfpointdecoratedpathlast}
}
}
\tikzset{
arc/.style = { x radius = \bundleX, y radius = \bundleY,
start angle = 90, delta angle = 180},
decomark/.style = {double=black,white,double distance=0.8pt, yshift = \bundleY, -, shorten <=-0.1pt},% arc style
edge bundle/.style n args={2}{execute at begin to={
\path[decorate, decoration = {markings,
mark=at position 0.5 with {\draw[decomark] (0,-\bundleY) circle({\bundleX} and \bundleY);
}}] (\tikztostart)--(\tikztotarget);
\draw[#1] (\tikztostart)--(\tikztotarget);
\draw[#2] (\tikztostart)--(\tikztotarget);
\path[decorate, decoration = {markings,
mark=at position 0.5 with {\draw[decomark] (0,0)
arc [arc];}}] (\tikztostart)--(\tikztotarget);}},
quad/.style={edge bundle={white,line width=2.5*\bundlesep}{decorate,decoration=quadlines}},
single/.style={edge bundle={white,line width=1.6pt}{black,line width=.8pt}},
bundle/.style={edge bundle={white,line
width=1.5*\bundlesep}{decorate,decoration=dublines}},
}
\begin{document}
\begin{tikzpicture}
\draw [bundle] (0,0) to (3,0);
\draw [single] (0,-0.5) to (3,-0.5);
\draw [quad] (0,-1) to (3,-1);
\draw [bundle] (4,0) to (7,-2);
\draw [quad] (5,-2) to (8,0);
\end{tikzpicture}
\end{document}
OLD ANSWER (to be removed if the new answer is closer to what you want.)
\documentclass[tikz, border=3.14mm]{standalone}
\usetikzlibrary{arrows.meta,decorations.markings,decorations}
\def\bundlesep{2pt}% distance between parallel edges
\def\bundleX{0.8*\bundlesep}
\def\bundleY{1.8*\bundlesep}% x and y radius of ellipse
\pgfdeclaredecoration{quadlines}{initial}
{%
\state{initial}[width=0pt,next state=final] {
\typeout{\pgfdecoratedpathlength}
\pgfcoordinate{quadstart1}{\pgfpoint{0pt}{-1*\bundlesep}}
\pgfcoordinate{quadstart2}{\pgfpoint{0pt}{-0.33*\bundlesep}}
\pgfcoordinate{quadstart3}{\pgfpoint{0pt}{0.33*\bundlesep}}
\pgfcoordinate{quadstart4}{\pgfpoint{0pt}{1*\bundlesep}}
}
\state{final}[width=\pgfdecoratedpathlength]
{
\pgfmoveto{\pgfpointanchor{quadstart1}{center}}
\pgfpathlineto{\pgfpoint{\pgfdecoratedpathlength}{-1*\bundlesep}}
\pgfmoveto{\pgfpointanchor{quadstart2}{center}}
\pgfpathlineto{\pgfpoint{\pgfdecoratedpathlength}{-0.33*\bundlesep}}
\pgfmoveto{\pgfpointanchor{quadstart3}{center}}
\pgfpathlineto{\pgfpoint{\pgfdecoratedpathlength}{0.33*\bundlesep}}
\pgfmoveto{\pgfpointanchor{quadstart4}{center}}
\pgfpathlineto{\pgfpoint{\pgfdecoratedpathlength}{1*\bundlesep}}
\pgfmoveto{\pgfpointdecoratedpathlast}
}
}
\tikzset{
arc/.style = { x radius = \bundleX, y radius = \bundleY,
start angle = 90, delta angle = 180},
arrow/.style = {{Bar[white, width = \bundlesep,length=0pt]}-{Bar[white,
width = \bundlesep, length = 0pt]}},% just a workaround fixing `double` bug
decomark/.style = {black, thick, yshift = \bundleY, -, shorten <=-0.1pt},% arc style
ellipse/.style = {
preaction = {decorate, decoration = {markings,
mark = at position 0.5 with {\draw[decomark, shorten >=-0.1pt] (0,0)
arc [arc, delta angle = -180];}}},
postaction = {decorate, decoration = {markings,
mark=at position 0.5 with {\draw[decomark] (0,0)
arc [arc];}}}},% consisting of 2 arcs
bundle/.style = {double, line width = 0.5pt, double distance = \bundlesep,
arrow, ellipse},
single/.style = {white, double = black, line width = 0.4pt,
double distance = 0.8pt, arrow, ellipse},
quad/.style={execute at begin to={
\path[decorate, decoration = {markings,
mark=at position 0.5 with {\draw[decomark] (0,-\bundleY) circle({\bundleX} and \bundleY);
\draw[white,line width=2.5*\bundlesep] (0,0) -- (1.3*\bundleX,0);}}] (\tikztostart)--(\tikztotarget);
\draw[decorate,decoration=quadlines] (\tikztostart)--(\tikztotarget);
\path[decorate, decoration = {markings,
mark=at position 0.5 with {\draw[decomark] (0,0)
arc [arc];}}] (\tikztostart)--(\tikztotarget);}},
}
\begin{document}
\begin{tikzpicture}
\draw [bundle] (0,0) -- (3,0);
\draw [single] (0,-0.5) -- (3,-0.5);
\draw [quad] (0,-1) to (3,-1);
\end{tikzpicture}
\end{document}
You can "stack" actions, so have a post action in a post action in a post action ... and so on. For some reason unknown to me, a double line as postaction to a double line produces not the desired result, but a way to wide line with wrong proportions. But one can stack post actions to draw lines alternating in black and white to achieve the look of four lines. As I kept doing something wrong with your nested definitions, I just copied everything into a draw command, I hope you'll be able to sort it out (Sorry!). I added two variantions for computing the line widths, absolute and relative.
Code
\documentclass[tikz, border=2pt]{standalone}
\usetikzlibrary{arrows.meta,decorations.markings}
%\pgfmathsetlengthmacro{\FirstBlack}{3.75pt}
%\pgfmathsetmacro{\LineFraction}{0.07}
%
%\pgfmathsetlengthmacro{\FirstWhite}{\FirstBlack*(1-2*\LineFraction)}
%\pgfmathsetlengthmacro{\SecondBlack}{\FirstBlack*(1-4*\LineFraction)/3+2*\FirstBlack*\LineFraction}
%\pgfmathsetlengthmacro{\SecondWhite}{\FirstBlack*(1-4*\LineFraction)/3}
%\pgfmathsetlengthmacro{\LoopX}{0.6*\FirstBlack}
%\pgfmathsetlengthmacro{\LoopY}{1.2*\FirstBlack}
\pgfmathsetlengthmacro{\FirstBlack}{4.3pt}
\pgfmathsetlengthmacro{\LineWidth}{0.4pt}
\pgfmathsetlengthmacro{\FirstWhite}{\FirstBlack-2*\LineWidth}
\pgfmathsetlengthmacro{\SecondBlack}{(\FirstBlack-4*\LineWidth)/3+2*\LineWidth}
\pgfmathsetlengthmacro{\SecondWhite}{(\FirstBlack-4*\LineWidth)/3}
\pgfmathsetlengthmacro{\LoopX}{0.6*\FirstBlack}
\pgfmathsetlengthmacro{\LoopY}{1.2*\FirstBlack}
\begin{document}
\begin{tikzpicture}
\draw
[ line width=\FirstBlack,
postaction=
{ draw, white, line width=\FirstWhite,
postaction=
{ draw, black, line width=\SecondBlack,
postaction=
{ draw, white, line width=\SecondWhite
}
}
},
preaction=
{ decorate,
decoration=
{ markings, mark=at position 0.5 with
{ \draw[ black, thick, yshift = \LoopY, -, shorten <=-0.1pt, shorten >=-0.1pt]
(0,0) arc
[ x radius = \LoopX, y radius = \LoopY,
start angle = 90, delta angle = 180, delta angle = -180];}}},
postaction=
{ decorate,
decoration=
{ markings, mark=at position 0.5 with
{ \draw[ black, thick, yshift = \LoopY, -, shorten <=-0.1pt, shorten >=-0.1pt]
(0,0) arc
[ x radius = \LoopX, y radius = \LoopY,
start angle = 90, delta angle = 180];}}}
]
(0,1) --(5,1);
\node[right] {\FirstBlack, \FirstWhite, \SecondBlack, \SecondWhite};
\end{tikzpicture}
\end{document}
Output
Edit 1: For a white outline one can simply add another line (here the \ZerothWhite
). One can also apply a post action to the arcs, so therefore they can also have a white outline:
Code
\documentclass[tikz, border=2pt]{standalone}
\usetikzlibrary{arrows.meta,decorations.markings}
%\pgfmathsetlengthmacro{\FirstBlack}{3.75pt}
%\pgfmathsetmacro{\LineFraction}{0.07}
%\pgfmathsetmacro{\WhiteFraction}{0.15}
%\pgfmathsetlengthmacro{\LoopFraction}{0.1}
%\pgfmathsetlengthmacro{\LoopSpaceFraction}{0.2}
%
%\pgfmathsetlengthmacro{\ZerothWhite}{\FirstBlack*(1+2*\WhiteFraction)}
%\pgfmathsetlengthmacro{\FirstWhite}{\FirstBlack*(1-2*\LineFraction)}
%\pgfmathsetlengthmacro{\SecondBlack}{\FirstBlack*(1-4*\LineFraction)/3+2*\FirstBlack*\LineFraction}
%\pgfmathsetlengthmacro{\SecondWhite}{\FirstBlack*(1-4*\LineFraction)/3}
%\pgfmathsetlengthmacro{\LoopX}{0.6*\FirstBlack}
%\pgfmathsetlengthmacro{\LoopY}{1.2*\FirstBlack}
%\pgfmathsetlengthmacro{\LoopWidth}{\LoopFraction*\FirstBlack}
%\pgfmathsetlengthmacro{\LoopWhite}{(\LoopFraction+2*\LoopFraction)*\FirstBlack}
\pgfmathsetlengthmacro{\FirstBlack}{4.3pt}
\pgfmathsetlengthmacro{\LineWidth}{0.4pt}
\pgfmathsetlengthmacro{\WhiteSpace}{1.2pt}
\pgfmathsetlengthmacro{\LoopWidth}{0.4pt}
\pgfmathsetlengthmacro{\LoopSpace}{1.2pt}
\pgfmathsetlengthmacro{\ZerothWhite}{\FirstBlack+2*\WhiteSpace}
\pgfmathsetlengthmacro{\FirstWhite}{\FirstBlack-2*\LineWidth}
\pgfmathsetlengthmacro{\SecondBlack}{(\FirstBlack-4*\LineWidth)/3+2*\LineWidth}
\pgfmathsetlengthmacro{\SecondWhite}{(\FirstBlack-4*\LineWidth)/3}
\pgfmathsetlengthmacro{\LoopX}{0.6*\FirstBlack}
\pgfmathsetlengthmacro{\LoopY}{1.2*\FirstBlack}
\pgfmathsetlengthmacro{\LoopWhite}{\LoopWidth+2*\LoopSpace}
\begin{document}
\begin{tikzpicture}
\draw
[ line width=\FirstBlack,
postaction=
{ draw, white, line width=\FirstWhite,
postaction=
{ draw, black, line width=\SecondBlack,
postaction=
{ draw, white, line width=\SecondWhite
}
}
},
preaction=
{ draw, white, line width=\ZerothWhite,
preaction=
{ decorate,
decoration=
{ markings, mark=at position 0.5 with
{ \draw[ black, line width=\LoopWidth, yshift = \LoopY, -, shorten <=-0.1pt, shorten >=-0.1pt]
(0,0) arc
[ x radius = \LoopX, y radius = \LoopY, start angle = 90, delta angle = 180,
delta angle = -180];
}
}
}
},
postaction=
{ decorate,
decoration=
{ markings, mark=at position 0.5 with
{ \draw[ white, line width=\LoopWhite, yshift = \LoopY, -, shorten <=-0.1pt, shorten >=-0.1pt,
postaction={draw, black, line width=\LoopWidth}
]
(0,0) arc
[ x radius = \LoopX, y radius = \LoopY, start angle = 90, delta angle = 180
];}}}
]
(0,1) --(5,1);
\node[right] {\FirstBlack, \FirstWhite, \SecondBlack, \SecondWhite};
\end{tikzpicture}
\end{document}