6

I'm trying to represent in LaTeX a binary tree implementing a map data structure. Someone told me TikZ was the best tool for that, but I'm failing to achieve my goal. I find TikZ... overhelming?

After exploring a lot of alternatives, this is the first time I think I'm close to an acceptable solution, at least for this very tree.

\documentclass{article}
\usepackage{tikz}
\usepackage{array}

\usetikzlibrary{arrows}

\begin{document}

\newcolumntype{x}[1]{>{\centering\arraybackslash\hspace{0pt}}p{#1}}

\newcommand{\tnode}[2]{
    \begin{tabular}{x{2em}|x{2em}}
        \tiny{}clave   & \tiny{}dato \\
        $#1$           & $#2$        \\\hline
        \tiny{}izq     & \tiny{}der  \\
                       &      
    \end{tabular}}

\newcommand{\leftedge}{[edge from parent path = {
                           ([xshift=0px, yshift=0px]\tikzparentnode.center)
                        -- (\tikzchildnode.north)}]}

\newcommand{\rightedge}{[edge from parent path = {
                           ([xshift=0px, yshift=-0px]\tikzparentnode.center)
                        -- (\tikzchildnode.north)}]}

\begin{tikzpicture}[
    level distance=7em,
    every node/.style = {align=center, font=\ttfamily, 
        inner sep=0pt, draw,},
    level 1/.style={sibling distance=20em},
    level 2/.style={sibling distance=12em},
    level 3/.style={sibling distance=6em},
    thick, *->, shorten >=2px, >=latex,
    ]
    \node (root) {\tnode{5}{15}}
        child[left] { node {\tnode{1}{11}} \leftedge{}
            child[right] { node {\tnode{3}{13}} \rightedge{}
                child[left]  { node {\tnode{2}{12}} \leftedge{}}
                child[right] { node {\tnode{4}{14}} \rightedge{}}
            }
        }
        child[right] { node {\tnode{7}{17}} \rightedge{}
            child[left] { node {\tnode{6}{16}} \leftedge{}}
        };
\end{tikzpicture}

\end{document}

I need left-child pointing arrows to depart from the center of the corresponding izq quarter, and the same for right and der. But I don't know how to set those anchors in my \tnode's. So I have decided to accept using a handwired amount of pixels to shift from a well-known anchor, as the node center is.

But then I find that the black circle I want as a starting tip is not centered around the edge starting point, so the circle position to shift it from is not fixed in a desirable way for my purposes.

I know that multipart nodes exist in TikZ and something called matrix too. I know \tikzmark exist also (for defining new anchors), but it seems it doesn't work well with \tabular.

At this point of desperation, I just want to fix my code in a way I can understand!

I have read something that should be a solution, but I'm sorry, I don't get it.

Is there a simpler solution? Or could someone help me to fix my picture along the lines of the linked one?

Thanks and sorry for the low quality of my question.

1
  • Yes, Tikz feels overwhelming in the beginning. However, it's basic ideas are simple and become more evident during using Tikz (practice). And honestly, you did fine in my view with the Tikz code you posted.
    – MS-SPO
    Dec 12, 2023 at 8:15

3 Answers 3

7

I think you want something like the following:

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{arrows.meta, matrix}

\newcommand{\matrixbody}[2]{
    \ttfamily\tiny clave \& \ttfamily\tiny dato \\ 
    $#1$                 \& $#2$                \\
    \ttfamily\tiny izq   \& \ttfamily\tiny der  \\
    {}                   \& {}                  \\
}

\tikzset{
    my matrix/.style={
        ampersand replacement=\&,
        matrix of nodes, 
        every node/.style={
            text width=3em,
            minimum height=1.25em,
            inner sep=0pt,
            align=center,
            execute at begin node={\strut} 
        },
        draw,
        thick,
        inner sep=0pt,
        row 3/.style={
            execute at end cell={
                \draw[very thin] 
                    (\tikzmatrixname-\the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn.north west) -- 
                    (\tikzmatrixname-\the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn.north east);
            }
        },
        column 2/.style={
            execute at end cell={
                \draw[very thin] 
                    (\tikzmatrixname-\the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn.north west) -- 
                    (\tikzmatrixname-\the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn.south west);
            }
        }
    },
    my tree/.style={
        every node/.style={
            my matrix
        },
        level distance=7em,
        level 1/.style={sibling distance=25em},
        level 2/.style={sibling distance=10em},
        level 3/.style={sibling distance=7.5em},
        edge from parent/.style={
            draw,
            thick, 
            {Circle[width=4pt, length=4pt]}-{Latex[]}, 
            shorten >=2pt, 
            shorten <=-2pt, 
        },
        left/.style={
            edge from parent path={
                (\tikzparentnode-4-1.center) --
                (\tikzchildnode.north)
            }
        },
        right/.style={
            edge from parent path={
                (\tikzparentnode-4-2.center) --
                (\tikzchildnode.north)
            }
        }
    },
}

\begin{document}
\begin{tikzpicture}[my tree]
    \node (root) {\matrixbody{5}{15}}
        child[left] { node {\matrixbody{1}{11}} 
            child[missing]
            child[right] { node {\matrixbody{3}{13}} 
                child[left]  { node {\matrixbody{2}{12}} } 
                child[right] { node {\matrixbody{4}{14}} } 
            }
        }
        child[right] { node {\matrixbody{7}{17}} 
            child[left] { node {\matrixbody{6}{16}} } 
            child[missing]
        };
\end{tikzpicture}
\end{document}

enter image description here

First of all, as you said, it might be a nice idea to use matrices here. I used the approach of this great answer to add the thin lines inside the matrix. It might be also possible to achieve this using another more sophisticated approach, but I think that this idea is still quite straight-forward.

Now that the nodes of the tree are essentially matrices of a very similar appearance, I thought that it would be a good idea to simplify typesetting their contents, which is why I created a custom command for this. Note that you need to escape the & and use the ampersand replacement key in order to make this work.

Finally, I used the approach you linked to exactly position the circles at the start of the connecting arrows. For this, it is best to use the arrows.meta library, because you can specify the size of the small circle exactly, which is necessary to set the shorten < option to the correct value.

In order to have the nodes placed a bit more nicely, I inserted two missing children.

4

Here's a Forest solution.

The \cdid macro makes up each matrix. Its styles are setup so that the first and the third row as well as the second and the fourth row are equal size so that the cross in the middle is in the geometric center as well and can simply be drawn by just using the four main compass anchors of the matrix.

Since I haven't found a solution to refer to a named cooridinate/node inside a matrix inside a forest node – i.e. combining the node walk !u with a suffix -1 or -2 –, I'm determining the start of the edge by calculating it in terms of the parent node.

Code

\documentclass{standalone}
\usepackage{forest}
\usetikzlibrary{positioning, ext.arrows}
\tikzset{
  cdid text/.style={
    shape=rectangle, node font=\tiny\ttfamily, text depth=+0pt, text height=+1em,
    text width=width("clave"), align=center},
  cdid val/.style={shape=rectangle, text width=width("00"), align=center},
  cdid matrix/.style={
    draw, thin, every outer matrix/.append style={inner sep=+0pt},
    every cell/.append code=\pgfpositionnodelater\relax, % disable forest
    append after command={% simple cross
      (\tikzlastnode.west) edge[very thin] (\tikzlastnode.east)
      (\tikzlastnode.north) edge[very thin] (\tikzlastnode.south)}}}
\newcommand*\cdid[2]{%
  \node[cdid text]{clave};\pgfmatrixnextcell\node[cdid text]{dato};\pgfmatrixendrow
  \node[cdid val]{$#1$};  \pgfmatrixnextcell\node[cdid val]{$#2$}; \pgfmatrixendrow
  \node[cdid text]{izq};  \pgfmatrixnextcell\node[cdid text]{der}; \pgfmatrixendrow
  \node[cdid val]{\phantom{$#1$}};\pgfmatrixnextcell
  \node[cdid val]{\phantom{$#2$}};\pgfmatrixendrow}
\forestset{
  cdid tree/.style={
    for tree={
      node options={matrix, cdid matrix},
      before typesetting nodes={+content=\cdid},
      edge={arrows={Centered Circle[scale=.75]}-Latex},
      edge path={
        \noexpand\path[path only](!u.south west)--
          coordinate[pos=\forestoption{n}>1?.75:.25](@) (!u.south east);
        \noexpand\path[\forestoption{edge}]([yshift=1ex]@)--
          (.child anchor)\forestoption{edge label};}},
    phantom/.append style={content=00}}}
\begin{document}
\begin{forest} cdid tree
[{5}{15}
  [{1}{11}
    [,phantom]
    [{3}{13}
      [{2}{12}]
      [{4}{14}]
    ]
  ]
  [{7}{17}
    [{6}{16}]
    [,phantom]
  ]
]
\end{forest}
\end{document}

Output

enter image description here

1

Here's yet another approach, which is almost yours. My strategy would have been quite similar as yours. Here's the change in two (and a half) steps:

  • change the parents anchor from .south to .south west
  • replace your shift in cartesian coordinates by polar coordinates (easier to visualize)
  • tweak the angle and radius a little
% ~~~ change anchor point AND apply shift in polar coordinates ~~~~~~~~~~
\newcommand{\leftedge}{[edge from parent path = {
                           ([shift=(40:6mm)]\tikzparentnode.south west)
                        -- (\tikzchildnode.north)}]}

Result:

result

Alternative:

  • keep your anchor .center
  • shift it outwards, again in polar coordinates

Just for readers reference about polar angles here:

  • 0 deg == positive x-direction (rightwards)
  • 90 deg == positive y-direction (upwards)
  • 180 deg = negative x-direction (leftwards)
  • 270 deg = negative y-direction (downwards)
\documentclass{article}
\usepackage{tikz}
\usepackage{array}

\usetikzlibrary{arrows}

\begin{document}

\newcolumntype{x}[1]{>{\centering\arraybackslash\hspace{0pt}}p{#1}}

\newcommand{\tnode}[2]{
    \begin{tabular}{x{2em}|x{2em}}
        \tiny{}clave   & \tiny{}dato \\
        $#1$           & $#2$        \\\hline
        \tiny{}izq     & \tiny{}der  \\
                       &      
    \end{tabular}}

% ~~~ change anchor point AND apply shift in polar coordinates ~~~~~~~~~~
\newcommand{\leftedge}{[edge from parent path = {
                           ([shift=(40:6mm)]\tikzparentnode.south west)
                        -- (\tikzchildnode.north)}]}

\newcommand{\rightedge}{[edge from parent path = {
                           ([shift=(140:6mm)]\tikzparentnode.south east)
                        -- (\tikzchildnode.north)}]}

\begin{tikzpicture}[
    level distance=7em,
    every node/.style = {align=center, font=\ttfamily, 
        inner sep=0pt, draw,},
    level 1/.style={sibling distance=20em},
    level 2/.style={sibling distance=12em},
    level 3/.style={sibling distance=6em},
    thick, *->, shorten >=2px, >=latex,
    ]
    \node (root) {\tnode{5}{15}}
        child[left] { node {\tnode{1}{11}} \leftedge{}
            child[right] { node {\tnode{3}{13}} \rightedge{}
                child[left]  { node {\tnode{2}{12}} \leftedge{}}
                child[right] { node {\tnode{4}{14}} \rightedge{}}
            }
        }
        child[right] { node {\tnode{7}{17}} \rightedge{}
            child[left] { node {\tnode{6}{16}} \leftedge{}}
        };
\end{tikzpicture}

\end{document}
2
  • Thanks, @ms-spo, but I think that changing the shift to polar coordinates doesn't solve the problem of black circular tips not centered around the arrow intended departure point. Dec 12, 2023 at 11:44
  • @FedericoPrat, fine. // I don't recognize the circular tips problem: looks perfect to me even at very high magnification. // I suggest to raise a new question on this subject only, while refering to this question.
    – MS-SPO
    Dec 12, 2023 at 12:12

You must log in to answer this question.

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