How to draw arrows from cell to cell at the borders of a table
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 tom{}
) for the columns where the arrows are to be placed. Use them{}
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:
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 thexparse
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:
\tikzmark
is from Adding a large brace next to a body of text.- The
\DrawArrow
macro is adapted from my earlier solution to How to draw arrows between parts of an equation to show the Math Distributive Property (Multiplication)?. - The
\globalTikzset
is from How to globally tikzset styles.
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:
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}
\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}
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.