[Tex/LaTex] How to draw arrows from cell to cell at the borders of a table

arrowstablestikz-pgf

How can I draw arrows from cell to cell at the borders of a table? More precisely I'd like to get something like this:

enter image description here

It would be nice to have a solution using TikZ.

Edit:

Since I have other tables like this in my document (but without the arrows, created with a regular tabular environment with custom column seps), it is important for me to have the same row sep and column sep in all tables. So it would be good to know how to adjust the TikZ-solutions to implement this.

\documentclass[a4paper,11pt]{article} 

\usepackage[T1]{fontenc} 
\usepackage{array}

\begin{document}
 \begin{tabular}{|m{1.3cm}|m{0.5cm}|m{0.5cm}|m{0.5cm}|m{0.5cm}|m{0.5cm}|m{0.1cm}}\cline{1-6}
  \footnotesize{ $x$ } & 0 & 1 & 2 & 3 &4 & \\\cline{1-6}
  \footnotesize{ $f(x)$} & 2 & 4&6 &8& 10& \\\cline{1-6}
 \end{tabular}
\end{document}

Best Answer

Update: Automated Solution

Here is a solution that automates the tediousness of the earlier solution. You use:

  • the MyTabular environment,
  • the M{} column type (as opposed to m{}) for the columns where the arrows are to be placed. Use the m{} for columns where there are no arrows.
  • \StartTopRow[]{} to indicate the start of the top row, provide the style for the top arrows, and supply the text that is to go above, and
  • \EndTopRow[]{} to indicate the end of the top row, provide the style for the bottom arrows, and also supply the text that is to go at the bottom arrows.

So, the following code:

\begin{MyTabular}{|m{1.3cm}|*5{M{0.5cm}|}M{0.1cm}|}\cline{1-6} \StartTopRow[red, thick]{\tiny$+1$}% specify top text
    \footnotesize{$x$}   & 0 & 1 & 2 & 3 & 4 \\\cline{1-6}     \EndTopRow[blue,thick]{\tiny$+2$}%   specify bottom text
    \footnotesize{$f(x)$}& 2 & 4 & 6 & 8 &10 \\\cline{1-6}
\end{MyTabular}

yields:

enter image description here

The collcell package is used to access each entry in the M{} columns and mark the points where the arrows are to be drawn with the appropriate \tikzmark nodes.

Notes:

  • This does require two runs. First one to determine the locations, and the second to do the drawing.
  • The values of \XShift and \ArcDistance may need to be tweaked via \renewcommand on a per table basis as shown in the commented code prior to the table.
  • To implement the two variants of \DrawArrow, I used \NewDocumentCommand from the xparse package as I prefer it's syntax, but this can be done without an additional pacakge as discussed in Defining starred versions of commands (* macro) if so desired.

Further Enhancements:

  • Still at least one more thing left to improve: The arrows should start and end offset from the middle of the column, not from the left point (plus offset) as is the case in the current solution. One solution would be to mark both the left and right of the text for each column, but there must be a simpler method. The simplest would be to put the text in node and use the .south anchor, but this affected the positioning of the text.

References:

Code:

\documentclass[a4paper,11pt]{article} 

\usepackage[T1]{fontenc} 
\usepackage{collcell}
\usepackage{xparse}
\usepackage{tikz}
\usetikzlibrary{calc}

% Adapted from https://tex.stackexchange.com/questions/47905/how-to-globally-tikzset-styles
\newcommand\globalTikzset[1]{%
    \begingroup%
        \globaldefs=1\relax%
        \tikzset{#1}%
    \endgroup%
}%
\tikzset{TopArrowStyle/.style={}}%
\tikzset{BottomArrowStyle/.style={}}%


\newcommand{\tikzmark}[2]{%
    \tikz[overlay,remember picture,baseline] \node [anchor=base] (#1) {\phantom{#2}};#2%
}

\newcounter{NumberOfTopColumns}%    Could just use one counter, but this handles case if we 
\newcounter{NumberOfBottomColumns}% ever have a different number of columns on top vs. bottom.

\newcommand*{\TopPrefix}{top}%
\newcommand*{\BottomPrefix}{bottom}%
\newcommand*{\CurrentTikzmarkPrefix}{}%  Gets redefined for top and bottom rows
\newcommand*{\IncrementColumnCounter}{}% Gets redefined for top and bottom rows
\newcommand*{\TopRowText}{}%
\newcommand*{\BottomRowText}{}%

\newcommand*{\StartTopRow}[2][]{%
    \globalTikzset{TopArrowStyle/.style={#1}}%
    \global\def\TopRowText{#2}%
    \setcounter{NumberOfTopColumns}{0}%
    \setcounter{NumberOfBottomColumns}{0}%
    \global\def\CurrentTikzmarkPrefix{\TopPrefix\arabic{NumberOfTopColumns}}%
    \global\def\IncrementColumnCounter{\stepcounter{NumberOfTopColumns}}%
}%
\newcommand*{\EndTopRow}[2][]{%
    \globalTikzset{BottomArrowStyle/.style={#1}}%
    \global\def\BottomRowText{#2}%
    \global\def\CurrentTikzmarkPrefix{\BottomPrefix\arabic{NumberOfBottomColumns}}%
    \global\def\IncrementColumnCounter{\stepcounter{NumberOfBottomColumns}}%
}%

\newcommand{\AddAppropriateTikzmark}[1]{%
    \tikzmark{\CurrentTikzmarkPrefix}{#1}%
    \IncrementColumnCounter%
}%

\newcolumntype{M}[1]{>{\collectcell\AddAppropriateTikzmark}m{#1}<{\endcollectcell}}%

\newenvironment{MyTabular}[1]{%
    \begin{tabular}{#1}%
}{%
    \end{tabular}%
    \addtocounter{NumberOfTopColumns}{-1}%
    \foreach \Column in {1,...,\arabic{NumberOfTopColumns}}{%
        \pgfmathtruncatemacro{\PreviousColumn}{\Column-1}%
        \DrawArrow[TopArrowStyle]{\TopPrefix\PreviousColumn}{\TopPrefix\Column}{\TopRowText}%
    }
    \addtocounter{NumberOfBottomColumns}{-1}%
    \foreach \Column in {1,...,\arabic{NumberOfBottomColumns}}{%
        \pgfmathtruncatemacro{\PreviousColumn}{\Column-1}%
        \DrawArrow*[BottomArrowStyle]{\BottomPrefix\PreviousColumn}{\BottomPrefix\Column}{\BottomRowText}%
    }
}%


\newcommand*{\XShift}{0.5ex}%
\newcommand*{\ArcDistance}{0.5cm}%
\NewDocumentCommand{\DrawArrow}{s O{} g g g g}{%
    \IfBooleanTF {#1} {% starred variant - draw arrows below
        \newcommand*{\OutAngle}{-60}%
        \newcommand*{\InAngle}{-120}%
        \newcommand*{\AnchorPoint}{south}%
        \newcommand*{\ShortenBegin}{2pt}%
        \newcommand*{\ShortenEnd}{1pt}%
        \newcommand*{\ArcVector}{-\ArcDistance}%
    }{% non-starred - draw arrows above
        \newcommand*{\OutAngle}{60}%
        \newcommand*{\InAngle}{120}%
        \newcommand*{\AnchorPoint}{north}%
        \newcommand*{\ShortenBegin}{0pt}%
        \newcommand*{\ShortenEnd}{0pt}%
        \newcommand*{\ArcVector}{\ArcDistance}%
    }%
    \begin{tikzpicture}[overlay,remember picture]
        \draw[
                ->, thick, distance=\ArcDistance,
                shorten <=\ShortenBegin, shorten >=\ShortenEnd,
                out=\OutAngle, in=\InAngle, #2
            ] 
                ($(#3.\AnchorPoint)+(2*\XShift,0)$) to 
                ($(#4.\AnchorPoint)+(\XShift,0)$);
        \node [] at ($(#3.\AnchorPoint)!0.5!(#4.\AnchorPoint) + (\XShift,\ArcVector)$) {#5};
    \end{tikzpicture}
}

\begin{document}
%\renewcommand*{\XShift}{0.5ex}%       Can be adjusted on a per table
%\renewcommand*{\ArcDistance}{0.5cm}%  basis as needed.

\begin{MyTabular}{|m{1.3cm}|*5{M{0.5cm}|}M{0.1cm}|}\cline{1-6}  \StartTopRow[red, thick]{\tiny$+1$}% specify top text
    \footnotesize{$x$}   & 0 & 1 & 2 & 3 & 4 \\\cline{1-6}      \EndTopRow[blue,thick]{\tiny$+2$}%   specify bottom text
    \footnotesize{$f(x)$}& 2 & 4 & 6 & 8 &10 \\\cline{1-6}
\end{MyTabular}

\bigskip\bigskip
\begin{tabular}{|m{1.3cm}|m{0.5cm}|m{0.5cm}|m{0.5cm}|m{0.5cm}|m{0.5cm}|m{0.1cm}}\cline{1-6}
    \footnotesize{$x$}    & 0 & 1 & 2 & 3 & 4 & \\\cline{1-6}
    \footnotesize{$f(x)$} & 2 & 4 & 6 & 8 & 10& \\\cline{1-6}
\end{tabular}
\end{document}

Manual Solution:

Leaving the older manual solution as it may be simpler to follow for new users.

Here is an illustration of a TikZ solution using the MWE given and \tikzmark to mark each point where you want the arrows to be drawn. Also shown below is the table as provided for comparison purposes to show that the spacing is the same:

enter image description here

Further Enhancements:

These have been implemented in the Automated Solution provided above:

  • If this is something that needs to be done often, much of this can be automated further, perhaps using the collcell pacakge.
  • The \foreach loop can also be simplified as the start node of the next arrow, is the end node of the previous so between subsequent iterations this could be stored and reused.

Code:

\documentclass[a4paper,11pt]{article} 

\usepackage[T1]{fontenc} 
\usepackage{array}

\usepackage{xparse}
\usepackage{tikz}
\usetikzlibrary{calc}

\newdimen{\Offset}
\newcommand{\tikzmark}[2]{%
    \settowidth{\Offset}{#2}%
    \tikz[overlay,remember picture,baseline] \node [anchor=base] (#1#2) {\phantom{#2}};#2%
}

\newcommand*{\XShift}{0.5ex}%
\newcommand*{\ArcDistance}{0.5cm}%
\NewDocumentCommand{\DrawArrow}{s O{} g g g g}{%
    \IfBooleanTF {#1} {% starred variant - draw arrows below
        \newcommand*{\OutAngle}{-60}%
        \newcommand*{\InAngle}{-120}%
        \newcommand*{\AnchorPoint}{south}%
        \newcommand*{\ShortenBegin}{2pt}%
        \newcommand*{\ShortenEnd}{1pt}%
        \newcommand*{\ArcVector}{-\ArcDistance}%
    }{% non-starred - draw arrows above
        \newcommand*{\OutAngle}{60}%
        \newcommand*{\InAngle}{120}%
        \newcommand*{\AnchorPoint}{north}%
        \newcommand*{\ShortenBegin}{0pt}%
        \newcommand*{\ShortenEnd}{0pt}%
        \newcommand*{\ArcVector}{\ArcDistance}%
    }%
    \begin{tikzpicture}[overlay,remember picture]
        \draw[
                ->, thick, distance=\ArcDistance,
                shorten <=\ShortenBegin, shorten >=\ShortenEnd,
                out=\OutAngle, in=\InAngle, #2
            ] 
                ($(#3.\AnchorPoint)+(2*\XShift,0)$) to 
                ($(#4.\AnchorPoint)+(\XShift,0)$);
        \node [] at ($(#3.\AnchorPoint)!0.5!(#4.\AnchorPoint) + (0,\ArcVector)$) {#5};
    \end{tikzpicture}
}

\begin{document}
%\renewcommand*{\XShift}{0.5ex}%       Can be adjusted on a per table
%\renewcommand*{\ArcDistance}{0.5cm}%  basis as needed.
\begin{tabular}{|m{1.3cm}|*5{m{0.5cm}|}m{0.1cm}|}\cline{1-6}
\footnotesize{$x$}   & \tikzmark{MarkX}{0} & \tikzmark{MarkX}{1} & \tikzmark{MarkX}{2} & \tikzmark{MarkX}{3} & \tikzmark{MarkX}{4} \\\cline{1-6}
\footnotesize{$f(x)$}& \tikzmark{MarkF}{2} & \tikzmark{MarkF}{4} & \tikzmark{MarkF}{6} & \tikzmark{MarkF}{8} &\tikzmark{MarkF}{10} \\\cline{1-6}
\end{tabular}

\foreach \x/\y in {0/1, 1/2, 2/3, 3/4}{%
    \DrawArrow[red]{MarkX\x}{MarkX\y}{\tiny$+1$}%
}
\foreach \x/\y in {2/4, 4/6, 6/8, 8/10}{%
    \DrawArrow*[blue]{MarkF\x}{MarkF\y}{\tiny$+2$}%
}

\bigskip\bigskip
 \begin{tabular}{|m{1.3cm}|m{0.5cm}|m{0.5cm}|m{0.5cm}|m{0.5cm}|m{0.5cm}|m{0.1cm}}\cline{1-6}
  \footnotesize{$x$}    & 0 & 1 & 2 & 3 & 4 & \\\cline{1-6}
  \footnotesize{$f(x)$} & 2 & 4 & 6 & 8 & 10& \\\cline{1-6}
 \end{tabular}
\end{document}