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}

enter image description here

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}

enter image description here


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

enter image description here


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}

Output

enter image description here

Tags:

Arc

Edge

Tikz Pgf