26

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}
2
  • Regarding your edit, are you willing to put all the tables into TikZ matrices, whether or not they are annotated, or do you want the TikZ style to emulate that of the tabulars? In either case, please post the code for the tabulars so we know what to mimic. Commented Mar 12, 2012 at 20:10
  • @AndrewStacey Yes, I want the TikZ style to emulate that of the tabular. See my edit for the code.
    – Anna
    Commented Mar 13, 2012 at 13:30

5 Answers 5

25

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}
1
  • Updated to allow stye specifications for top and bottom arrows as opposed to the hard coded values. Commented Mar 15, 2012 at 4:25
18
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{mathabx}
\def\MCt{\multicolumn{2}{c}{$\stackrel{+1}{\curvearrowright}$}}
\def\MCb{\multicolumn{2}{c}{\rule{0pt}{4ex}$\stackrel{\displaystyle\curvearrowbotright}{\scriptstyle+2}$}}
\def\MC{\multicolumn{1}{c}{}}
\begin{document}

\begin{tabular}{|l|*5{r|}}
 \MC  & \MCt & \MCt\\[\dimexpr -\normalbaselineskip-2.6pt]
 \MC  & \MC  & \MCt & \MCt\\\hline
$x$   & 0 & 1 & 2 & 3 & 4 \\\hline
$f(x)$& 2 & 4 & 6 & 8 &10 \\\hline
\MC  & \MCb & \MCb\\[\dimexpr -\normalbaselineskip-2.1ex]
\MC  & \MC  & \MCb & \MCb
\end{tabular}

\end{document}

enter image description here

7
  • Thanks. Is it also possible to control the length and the curvature of the arrows?
    – Anna
    Commented Mar 11, 2012 at 14:08
  • 2
    only with a trick: define an optional argument for \MCb and \MCt which sets the horizontal stretch, eg: \MCb[2]. The default should be 1. This stretch can then be used for \scalebox[1]{#1}{$..the arrow..$}
    – user2478
    Commented Mar 11, 2012 at 14:15
  • @Herbert You could draw the arrows with TikZ, for example \newcommand{\arrA}{\begin{tikzpicture} \draw (0,0) edge[->,bend left=70] (0.4,0); \end{tikzpicture}} \newcommand{\arrB}{\begin{tikzpicture} \draw (0,0) edge[->,bend right=70] (0.4,0); and replace your arrows by \arrA and \arrB
    – student
    Commented Mar 11, 2012 at 15:03
  • @student: why should I use a big package when it can be done with default macros?
    – user2478
    Commented Mar 11, 2012 at 17:06
  • @Herbert, then you can easily change the curvature of the arrows as Anna requested.
    – student
    Commented Mar 11, 2012 at 17:08
11

Here's a quick way:

\documentclass{minimal}
\usepackage{tikz}
\usetikzlibrary{matrix}
\begin{document}
\begin{tikzpicture}
  \matrix[matrix of math nodes,draw, column sep=1em,row sep=.5mm] (mx) {
      x & 0 & 1 & 2 & 3 & 4 \\
    f(x)& 2 & 4 & 6 & 8 & 10 \\
  };
  \path[->,shorten >=2pt]
    \foreach \from/\to in {2/3,3/4,4/5,5/6} {
      ([yshift=2mm]mx-1-\from.north) edge[bend left]
        node[above] {$\scriptstyle+1$} ([yshift=2mm]mx-1-\to.north)
      ([yshift=-2.5mm]mx-2-\from.south) edge[bend right]
        node[below] {$\scriptstyle+2$} ([yshift=-2.5mm]mx-2-\to.south)
    };
\foreach \x in {2,...,6}{
\draw ([xshift=-0.5em]mx.north west -| mx-1-\x.west) -- ([xshift=-0.5em]mx.south west -| mx-1-\x.west);
};
\draw (mx.west) -- (mx.east);
\end{tikzpicture}
\end{document}

Although doesn't look exactly the way you want:

EDIT by percusse: I have added a simple way of drawing borders. It's not that elegant but still I think the borderless case is clearer.

enter image description here

5
  • Thanks. However I need the horizontal and vertical lines of the table.
    – Anna
    Commented Mar 11, 2012 at 13:53
  • @Anna: Well, there's at least now a template for other TikZ answerers ;)
    – morbusg
    Commented Mar 11, 2012 at 13:56
  • @morbusg You could add two lines by: \draw (mx-1-1.south west) -- (mx-1-6.south east); and \draw (mx-1-2.north west) -- (mx-2-2.south west); but I don't see how to get the other lines this way...
    – student
    Commented Mar 11, 2012 at 14:03
  • @morbusg I think I can add the line drawings. Do you mind if I edit your answer.
    – percusse
    Commented Mar 11, 2012 at 14:11
  • @percusse: of course not, go ahead :)
    – morbusg
    Commented Mar 11, 2012 at 14:33
10

Here's my TikZ version. It uses experimental code that was initially developed in answer to https://tex.stackexchange.com/a/14092/86 and further worked on. It's now available as part of the TeX-SX launchpad files. You'll need the matrixcells.dtx file, and run tex matrixcells.dtx to generate the .sty file.

What this does is to add to a cell of a TikZ matrix a rectangular node that is the size of the cell itself. The node in the cell can be smaller than the cell, whose size depends on the other nodes in that row and column. This can be used to work with elements that need to refer to the cell as seen from the outside, rather than the contents of the cell itself.

Experimenting with this answer, I noticed that the results from putting draw on the matrix itself do not quite match with the internal cells. Not sure why, but the fix is simply to use the cells to draw the outer box, not the matrix itself.

Here's the code:

\documentclass{article}
%\url{https://tex.stackexchange.com/q/47610/86}
\usepackage{tikz}
\usetikzlibrary{matrix}
\usepackage{matrixcells}

\begin{document}
\begin{tikzpicture}
\matrix[matrix of math nodes,hlines,column sep=1em,row sep=.5mm] (mx) {
      x & 0 & 1 & 2 & 3 & 4 \\ \hline
    f(x)& 2 & 4 & 6 & 8 & 10 \\
  };
\draw (mx-cell-1-1.north west) rectangle (mx-cell-2-6.south east);
\foreach \col in {1,2,3,4,5} {
  \draw (mx-cell-1-\col.north east) -- (mx-cell-2-\col.south east);
  \unless\ifnum\col=1\relax
   \pgfmathtruncatemacro{\coll}{\col+1}
   \draw[->,shorten >=.5ex,shorten <=.5ex] (mx-cell-1-\col.north) to[out=60,in=120] node[above] {\(\scriptstyle+1\)} (mx-cell-1-\coll.north);
   \draw[->,shorten >=.5ex,shorten <=.5ex] (mx-cell-2-\col.south) to[out=-60,in=-120] node[below] {\(\scriptstyle+2\)} (mx-cell-2-\coll.south);
  \fi
}
\end{tikzpicture}
\end{document}

Following morbusg's answer, we use a matrix of math nodes. The matrixcells package defines an hlines key which allows us to automatically put the horizontal middle line in using \hline. This has the side effect of defining the cell nodes which have the names mx-cell-<row>-<col>. Next, we draw the outer rectangle using the corner cells. Lastly, we loop over the columns drawing the divider lines and the arrows (with an exception for the first column on the arrows). The arrows are shortened slightly to give a better effect, and the labels on the arrows are typeset in \scriptstyle to de-emphasise them slightly.

Here's the result:

TikZ matrix with arrows between cells

2

With {NiceArray} of nicematrix and TikZ to draw the arrows.

\documentclass{article}
\usepackage{nicematrix,tikz}
\usetikzlibrary{bending}

\begin{document}

$\begin{NiceArray}{w{l}{15mm}*{6}{w{c}{5mm}}}[hvlines]
\CodeBefore
  \begin{tikzpicture} [red,->,shorten > = 3pt, shorten < = 3pt]
    \foreach \i in {3,...,\value{jCol}} 
      { \draw (1-|\inteval{\i-1}.5) to [bend left] node [above] {\tiny $+1$} (1-|\i.5) ;  
        \draw (3-|\inteval{\i-1}.5) to [bend right] node [below] {\tiny $+2$} (3-|\i.5) ; }
  \end{tikzpicture}
\Body
    x    & 0 & 1 & 2 & 3 & 4 & 5 \\
    f(x) & 2 & 4 & 6 & 8 & 10 & 12\\
\end{NiceArray}$

\end{document}

Output of the above code

3
  • I think you forgot to load xfp if you're using \inteval.
    – Sandy G
    Commented Oct 18, 2023 at 11:59
  • @Sandy G: I think that \inteval is now in the LaTeX kernel. Commented Oct 18, 2023 at 13:34
  • I was unaware of that update. Thanks for the info.
    – Sandy G
    Commented Oct 18, 2023 at 17:14

You must log in to answer this question.

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