22

I'm looking for a simple way to draw the following picture using TikZ. Namely, I would like to only specify the coordinates of the cities and then to connect them by edges using a \foreach loop. This would be easy if there were no directed edges at this picture:

enter image description here

So, more precisely, my question is how to modify the second \foreach below to get a picture above.

\begin{scope}
\foreach \x/\y/\name/\label/\where in {1.2/1.4/a/{El Paso}/below,     
   1/3.2/b/Albuquerque/above, 2.5/3/c/Amarillo/below, 3.8/4/d/Wichita/above,  
   4.3/3.3/e/Tulsa/45, 5.2/2.8/f/{Little Rock}/right, 4.2/2/g/Dallas/left, 
   4.8/1/h/Houston/right, 3.6/0.6/i/{San Antonio}/below} {
  \draw (\x,\y) circle (.4mm);
\node[draw,circle,minimum size=2mm,inner sep=0mm,label=\where:{\footnotesize \label}] (\name) at (\x,\y) {};
}

\foreach \s/\t in {a/b, b/c, c/e, e/d, e/f, f/g, h/g, h/i}
  \path[draw] (\s) edge (\t);
\end{scope}
1

3 Answers 3

15

If you create a simple "decoration" which simply draws a stroke from the initial to the final point, you can use the raise option for decorated paths to get a parallel path. Applying shorten > and shorten < to this raised path, and putting arrow tips on it, you get the desired result:

Result

Here is the code (the style parallel arrow takes care of all):

\documentclass{article}
\usepackage{tikz}
\begin{document}
\usetikzlibrary{decorations}
\pgfdeclaredecoration{sl}{initial}{
  \state{initial}[width=\pgfdecoratedpathlength-1sp]{
     \pgfmoveto{\pgfpointorigin}
  }
  \state{final}{
     \pgflineto{\pgfpointorigin}
    }
}

\tikzset{parallel arrow/.style={->, 
     shorten >=2mm, shorten <=2mm, 
     decoration={sl,raise=1mm},decorate}}

\begin{tikzpicture}
\begin{scope}
\foreach \x/\y/\name/\label/\where in {1.2/1.4/a/{El Paso}/below,     
   1/3.2/b/Albuquerque/above, 2.5/3/c/Amarillo/below, 3.8/4/d/Wichita/above,  
   4.3/3.3/e/Tulsa/45, 5.2/2.8/f/{Little Rock}/right, 4.2/2/g/Dallas/left, 
   4.8/1/h/Houston/right, 3.6/0.6/i/{San Antonio}/below} {
  \draw (\x,\y) circle (.4mm);
\node[draw,circle,minimum size=2mm,inner sep=0mm,label=\where:{\footnotesize \label}] (\name) at (\x,\y) {};
}

\foreach \s/\t in {a/b, b/c, c/e, e/d, e/f, f/g, h/g, h/i} {
  \path[draw] (\s) edge (\t);
  \path[draw] (\s) edge[parallel arrow] (\t);
  \path[draw] (\t) edge[parallel arrow] (\s);}
\end{scope}
\end{tikzpicture}
\end{document}

Update

I added the numbers in each route. I had some fun parametrizing their position so that they can be all put in a single loop.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations,calc}
\begin{document}
\pgfdeclaredecoration{sl}{initial}{
  \state{initial}[width=\pgfdecoratedpathlength-1sp]{
     \pgfmoveto{\pgfpointorigin}
  }
  \state{final}{
     \pgflineto{\pgfpointorigin}
    }
}
\tikzset{parallel arrow/.style={latex-,
     shorten >=2mm, shorten <=2mm, 
     decoration={sl,raise=1mm},decorate}}

\begin{tikzpicture}
\begin{scope}
\foreach \x/\y/\name/\label/\where in {1.2/1.4/a/{El Paso}/below,     
   1/3.2/b/Albuquerque/above, 2.5/3/c/Amarillo/below, 3.8/4/d/Wichita/above,  
   4.3/3.3/e/Tulsa/45, 5.2/2.8/f/{Little Rock}/right, 4.2/2/g/Dallas/left, 
   4.8/1/h/Houston/right, 3.6/0.6/i/{San Antonio}/below} {
  \draw (\x,\y) circle (.4mm);
\node[draw,circle,minimum size=2mm,inner sep=0mm,label=\where:{\footnotesize \label}] (\name) at (\x,\y) {};
}

\tikzset{edge label/.style={font=\tiny, inner sep=1.6mm},
ab/.style={edge label, left},
ba/.style={edge label, right},
bc/.style={edge label, above},
cb/.style={edge label, below},
ce/.style={bc},
ec/.style={cb},
ed/.style={ab},
de/.style={ba},
ef/.style={edge label, below left, inner sep=1mm},
fe/.style={edge label, above right, inner sep=1mm},
fg/.style={edge label, above left, inner sep=1mm},
gf/.style={edge label, below right, inner sep=1mm},
hg/.style={ab},
gh/.style={ba},
hi/.style={bc},
ih/.style={cb}
}  

\foreach \s/\t/\from/\to in {a/b/9/10, b/c/8/11, c/e/7/12, e/d/6/5, 
                             e/f/13/4, f/g/14/3, h/g/15/2, h/i/16/1} {
  \path[draw] (\s) edge (\t) ($(\s)!.5!(\t)$) node[\s\t] {\from} node[\t\s]{\to};
  \path[draw] (\s) edge[parallel arrow] (\t);
  \path[draw] (\t) edge[parallel arrow] (\s);
}
\end{scope}
\end{tikzpicture}
\end{document}

Result

1
  • Thank you very much, @JLDiaz! This is exactly what I was looking for. Now I should learn these decorations. =) Dec 7, 2012 at 9:53
10

JLDiaz's answer is excellent, this is just a small addition to it:

I wanted to avoid hard coding the values for the directed edges, so I thought I'd use the auto placement option, which places nodes to the side of a path. However, it turns out that this only happens in 45 degree steps: Nodes are either placed using their south anchor, or their north west anchor, etc., but no intermediate values. For this application, that's not precise enough. However, if you redefine two internal functions, you can get degree-accuracy placement of the nodes:

\makeatletter
\def\tikz@auto@anchor{%
    \pgfmathtruncatemacro\angle{atan2(\pgf@x,\pgf@y)-90}
    \edef\tikz@anchor{\angle}%
}

\def\tikz@auto@anchor@prime{%
    \pgfmathtruncatemacro\angle{atan2(\pgf@x,\pgf@y)+90}
    \edef\tikz@anchor{\angle}%
}
\makeatother

To get an even offset of the labels from the centre line, I used an ellipse node shape.

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations,calc, shapes.geometric}

\makeatletter
\def\tikz@auto@anchor{%
    \pgfmathtruncatemacro\angle{atan2(\pgf@x,\pgf@y)-90}
    \edef\tikz@anchor{\angle}%
}

\def\tikz@auto@anchor@prime{%
    \pgfmathtruncatemacro\angle{atan2(\pgf@x,\pgf@y)+90}
    \edef\tikz@anchor{\angle}%
}
\makeatother

\begin{document}
\pgfdeclaredecoration{sl}{initial}{
  \state{initial}[width=\pgfdecoratedpathlength-1sp]{
     \pgfmoveto{\pgfpointorigin}
  }
  \state{final}{
     \pgflineto{\pgfpointorigin}
    }
}
\tikzset{parallel arrow/.style={latex-,
     shorten >=2mm, shorten <=1mm, 
     decoration={sl,raise=1mm},decorate}}

\begin{tikzpicture}
\begin{scope}
\foreach \x/\y/\name/\label/\where in {1.2/1.4/a/{El Paso}/below,     
   1/3.2/b/Albuquerque/above, 2.5/3/c/Amarillo/below, 3.8/4/d/Wichita/above,  
   4.3/3.3/e/Tulsa/30, 5.2/2.8/f/{Little Rock}/right, 4.2/2/g/Dallas/left, 
   4.8/1/h/Houston/right, 3.6/0.6/i/{San Antonio}/below} {
  \draw (\x,\y) circle (.4mm);
\node[draw,circle,minimum size=2mm,inner sep=0mm,label=\where:{\footnotesize \label}] (\name) at (\x,\y) {};
}

\tikzset{
    edge label/.style={
        font=\tiny,
        auto=right,
        ellipse,inner sep=1mm,
    }
}  

\foreach \s/\t/\from/\to in {a/b/9/10, b/c/8/11, c/e/7/12, e/d/6/5, 
                             e/f/13/4, f/g/14/3, h/g/15/2, h/i/16/1} {
  \path[draw] (\s) edge (\t);
  \path[draw] (\s) edge[parallel arrow]
    node [edge label] {\from} (\t);
  \path[draw] (\t) edge[parallel arrow]
  node [edge label] {\to}(\s);
}
\end{scope}
\end{tikzpicture}
\end{document}
1
  • Thank you, @Jake! It is nice to have an alternative way to draw this. Dec 7, 2012 at 9:55
9

Here is another method using to path and calc library.

An edge and its parallel arrows with labels are drawn by a single to operation:

\draw (from) to[edge with values=XX and YY] (to);

(where XX and YY are from->to and to->from labels. They are automatically positioned.) enter image description here

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\tikzset{
  % store of parameters
  parallel distance/.store in=\paradist,
  parallel shorten/.store in=\parashorten,
  parallel label distance/.store in=\paralabdist,
  % default values
  parallel distance=1mm, % distance of parallel arrows from link
  parallel shorten=3mm, % to shorten parallel arrows
  parallel label distance=2.5mm, % distance of label from link
  % main style
  edge with values/.style args={#1 and #2}{
    to path={
      \pgfextra{
        \pgfinterruptpath
        % middle point
        \coordinate (m) at ($(\tikztostart)!.5!(\tikztotarget)$);
        % from-to arrow
        \draw[-stealth,shorten >=\parashorten,shorten <=\parashorten]
        ($(\tikztostart)!\paradist!-90:(\tikztotarget)$)
        --
        ($(\tikztotarget)!\paradist!90:(\tikztostart)$)
        % middle of from-to arrow
        coordinate[pos=.5](m from);
        % label of from-to arrow
        \node[font=\tiny] at ($(m)!\paralabdist!(m from)$){#1};
        % to-from arrow
        \draw[-stealth,shorten >=\parashorten,shorten <=\parashorten]
        ($(\tikztotarget)!\paradist!-90:(\tikztostart)$)
        --
        ($(\tikztostart)!\paradist!90:(\tikztotarget)$)
        % middle of to-from arrow
        coordinate[pos=.5](m to);
        % label of to-from arrow
        \node[font=\tiny] at ($(m)!\paralabdist!(m to)$){#2};
        \endpgfinterruptpath
      }
      % link
      (\tikztostart) -- (\tikztotarget)
    },
  },
}

\begin{document}
\begin{tikzpicture}
  \begin{scope}
    \foreach \x/\y/\name/\label/\where in {%
      1.2/1.4/a/{El Paso}/below,%     
      1/3.2/b/Albuquerque/above,%
      2.5/3/c/Amarillo/below,%
      3.8/4/d/Wichita/above,%
      4.3/3.3/e/Tulsa/45,%
      5.2/2.8/f/{Little Rock}/right,%
      4.2/2/g/Dallas/left,%
      4.8/1/h/Houston/right,%
      3.6/0.6/i/{San Antonio}/below%
    } {
      \draw (\x,\y) circle (.4mm);
      \node[draw,circle,minimum size=2mm,inner sep=0mm,
      label=\where:{\footnotesize \label}]
      (\name) at (\x,\y) {};
    }

    \foreach \s/\t/\from/\to in {
      a/b/10/9,
      b/c/11/8,
      c/e/12/7,
      e/d/5/6, 
      e/f/13/4,
      f/g/14/3,
      h/g/2/15,
      h/i/16/1
    } {
      \path[draw] (\s) to[edge with values=\from{} and \to] (\t);
    }
  \end{scope}
\end{tikzpicture}
\end{document}
4
  • 1
    Ah, very elegant!
    – Jake
    Dec 5, 2012 at 22:23
  • I was using this code for some years now. Thanks. Today I noticed that the decoration starts within the nodes. In all examples above this is masked by the shorten, but his won't work if the nodes on both sides of the line aren't equally sized (and also round). You can check by replacing your picture above with only \node[draw] (a) {large} +(5,0) node[draw] {large} edge[edge with values= x and y] (a);. Any chance you can fix this?
    – ziggystar
    Mar 21, 2017 at 12:09
  • @ziggystar My answer works with little circular nodes with no content (as required in the question). You can ask a new question... Mar 21, 2017 at 13:40
  • Here's a new question: tex.stackexchange.com/questions/359594/…
    – ziggystar
    Mar 21, 2017 at 14:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .