Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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}
share|improve this question
    
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. –  Loop Space Mar 12 '12 at 20:10
    
@AndrewStacey Yes, I want the TikZ style to emulate that of the tabular. See my edit for the code. –  Anna Mar 13 '12 at 13:30
add comment

4 Answers 4

up vote 21 down vote accepted

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 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 http://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}
share|improve this answer
    
Updated to allow stye specifications for top and bottom arrows as opposed to the hard coded values. –  Peter Grill Mar 15 '12 at 4:25
add comment
\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

share|improve this answer
    
Thanks. Is it also possible to control the length and the curvature of the arrows? –  Anna Mar 11 '12 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..$} –  Herbert Mar 11 '12 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 Mar 11 '12 at 15:03
    
@student: why should I use a big package when it can be done with default macros? –  Herbert Mar 11 '12 at 17:06
    
@Herbert, then you can easily change the curvature of the arrows as Anna requested. –  student Mar 11 '12 at 17:08
show 2 more comments

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

share|improve this answer
    
Thanks. However I need the horizontal and vertical lines of the table. –  Anna Mar 11 '12 at 13:53
    
@Anna: Well, there's at least now a template for other TikZ answerers ;) –  morbusg Mar 11 '12 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 Mar 11 '12 at 14:03
    
@morbusg I think I can add the line drawings. Do you mind if I edit your answer. –  percusse Mar 11 '12 at 14:11
    
@percusse: of course not, go ahead :) –  morbusg Mar 11 '12 at 14:33
add comment

Here's my TikZ version. It uses experimental code that was initially developed in answer to http://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{http://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

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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