13

In my job I often draw network diagrams. Connections are usually doubled for safe redundant operation. Also, connections can be bundled for performance reasons, so there may be four connections between two devices.

I need to indicate that two edges belong to the same bundle. We say port channel or Ethernet channel for such aggregated connections between switches. Usually, they are indicated by a summarising ellipse in the middle of parallel lines.

It would be very helpful for network engineers who use TikZ instead of MS Visio to have an edge style for channels.

I started to create one:

  • parallel edges, 2 or 4 (other numbers rarely occur)
  • ellipse split into two arcs
    • either drawn as preaction and postaction
    • or on background and foreground layer
    • a border around lines is good for indicating a crossing (we could use a double style for line and arc)

Here is my code for a start:

\documentclass[tikz, border=2pt]{standalone}
\usetikzlibrary{arrows.meta,decorations.markings}
\def\bundlesep{2pt}% distance between parallel edges
\def\bundleX{0.8*\bundlesep}
\def\bundleY{1.8*\bundlesep}% x and y radius of ellipse
\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}
}
\begin{document}
\begin{tikzpicture}
  \draw [bundle] (0,0)    -- (3,0);       
  \draw [single] (0,-0.5) -- (3,-0.5);       
\end{tikzpicture}
\end{document}

bundled edges

These are two examples:

  1. I used a double path for two parallel edges. I wish there was a quad path! How can I have 4 lines?

  2. This shows how the bundling circle, drawn as ellipse, has a foreground part and a background part behind the line, gaps help to see the crossing. Also the arc should have some white space around for the other crossing.

Throwing this together and improving it to have an easy to use edge style, what would be the best way to draw a bundle of edges with a "3D" circle, that is, an ellipse around the 2 or 4 edges bundle?

11
+50

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

  • Instead of using a decoration, one draw the parallel lines also with the calc syntax. Please let me know if you would like to see an example. I chose decorations since this may be easier to customize if you ever decide to also draw bent paths. – user121799 Oct 20 '18 at 20:14
  • Great, thanks! It also works fine when the lines are not horizontal. A white outline around the lines in example #1 and the arc in general (I guess also a decoration, like Tom showed above) would be perfect. – Stefan Kottwitz Oct 21 '18 at 12:57
  • Yes, that makes is always better to see what is crossing above and what is below. As in Tom's image at the end. But don't worry, it's again a decoration. Thanks! – Stefan Kottwitz Oct 21 '18 at 14:54
  • @StefanKottwitz I have rewritten the answer and made it more universal (i.e. there are some basic things stored in edge bundle which gets fed with the specifics for single, bundle and quad. Please have a look and tell me whether those go in the right direction. – user121799 Oct 21 '18 at 15:24
9

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

  • I think that the double works by doubling the current line width. So if you stack them, it keeps doubling them, I think. – cfr Nov 29 '15 at 20:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.