6

Is there a way to construct a tree whose children are located at specified positions, so that TikZ computes the locations of the other nodes in the tree based on the level distance and sibling distance?

I ask because, ultimately, I have a matrix of nodes and would like to grow a tree into the second column's vector from the righthand side. A minimal working example of what I have so far is:

\documentclass{standalone}
\usepackage{tikz}

\usetikzlibrary{positioning,matrix}

\begin{document}

\tikzset{
  img/.initial={},
  img/.value required,
  %
  image placeholder/.style={%
    execute at end node=\phantom{\pgfuseimage{\pgfkeysvalueof{/tikz/img}}}}
}

\pgfdeclareimage[height=7em]{ballot}{fptp-ballot}

\begin{tikzpicture}[every node/.style={draw=black},
                    every matrix/.style={ampersand replacement=\&}]
  \matrix (ballot counts)
          [matrix of nodes, nodes in empty cells, cells={right},
           column 1/.append style={%
             nodes={scale=0.5}, image placeholder, img=ballot}]
  {
    \& Alice: $0$ \\
    \& Bob: $0$ \\
    \& Charlie: $0$ \\
    \& Dave: $0$ \\
  };

  \matrix (ballot box)
          [node distance=2 and 2, right=of ballot counts,
           matrix of nodes, nodes in empty cells,
           cells={image placeholder, img=ballot}]
  {
    \& \\
    \& \\
  };

  \matrix (max tree)
          [matrix anchor=west]
          at (ballot counts.east)
  {
    \node {root} [grow=left, level/.style={sibling distance=7em/#1}]
      child {node {left}
        child {node {}}
        child {node {}}}
      child {node {right}
        child {node {}}
        child {node {}}}; \\
  };
\end{tikzpicture}

\end{document}

minimal working example of progress toward tree growing into matrix

I would like to modify this diagram so that the leaves of max tree attach to the four entries in second column of ballot counts (i.e., Alice: 0, ...).

In an earlier version of this question, @TomBombadil suggested that I could use at to explicitly position the leaves of the tree. The problem with that is that then the interior nodes will also have to be positioned explicitly. I was hoping for some way to leverage level distance and sibling distance.

Edit: Based on @percusse's comment that "growing the tree and putting nodes left of each sibling" might be an answer, I came up with the following MWE that does, more or less, what I want. (@percusse: Is this what you were getting at, or were you thinking something different?)

\documentclass{standalone}
\usepackage{tikz}

\usetikzlibrary{calc,positioning,matrix,fit}

\begin{document}

\tikzset{
  img/.initial={},
  img/.value required,
  %
  image placeholder/.style={%
    execute at end node=\phantom{\pgfuseimage{\pgfkeysvalueof{/tikz/img}}}}
}

\pgfdeclareimage[height=7em]{ballot}{fptp-ballot}

\tikzstyle{ballot count} = [matrix of nodes, nodes in empty cells,
                            right, inner sep=0em,
                            cells={right, inner sep=0.3333em},
                            column 1/.append style={%
                              image placeholder, img=ballot, nodes={scale=0.5}}]

\begin{tikzpicture}[every node/.style={draw},
                    every matrix/.style={ampersand replacement=\&}]
  % Construct a fake ballot count ``leaf'' to measure its height.
  \matrix (tmp) [ballot count, overlay, 
                 draw=none, nodes={draw=none}]
  { \\ };

  % Measure the height of the fake ``leaf''.
  \path let \p{childheight} = ($(tmp.north)-(tmp.south)$) in
    % Use the height to set sibling distance so that
    % there is no gap between the leaves of the tree.
    [level/.append style={sibling distance=2*\y{childheight}/#1,
                          level distance=2cm*#1}]
    node {root} [grow=left]
      child {node {left}
        child [child anchor=east] foreach \name in {Alice, Bob}
          {node (\name's count) [matrix, ballot count]
             { \& \name: $0$ \\ }}}
      child {node {right}
        child [child anchor=east] foreach \name in {Charlie, Dave}
          {node (\name's count) [matrix, ballot count]
             { \& \name: $0$ \\ }}};

  % Build the bounding box that contains all leaves.
  \node (ballot counts)
        [inner sep=0em, 
         fit=(Alice's count) (Bob's count)
             (Charlie's count) (Dave's count)] {};

  % Position a matrix to the right of the leaves.
  \matrix (ballot box)
          [node distance=2 and 2, right=of ballot counts,
           matrix of nodes, nodes in empty cells,
           cells={image placeholder, img=ballot}]
  {
    \& \\
    \& \\
  };
\end{tikzpicture}

\end{document}

enter image description here

This approach has several drawbacks:

  1. I have to create a fake leaf, named tmp, to measure the height of an arbitrary leaf. It would be better if I could somehow measure the height of a leaf and then retroactively pass this height to the sibling distance for the tree.

  2. Since the leaves are no longer within a matrix, I have to explicitly construct a bounding box in order to position ballot box where I want it to be. Also, the code for the leaves is now spread among the two subtrees; this complicates the description of the tree edges and non-leaf nodes, and requires that some of the code is duplicated across subtrees.

I guess, in general, the code is not as clean as I would have liked; I'm still hoping that there is a cleaner solution. But perhaps I just need to learn to be less greedy. :)

  • you may find some informations about that in this thread – sylcha Jun 09 '12 at 00:49
  • i use another comment because it is too late to edit the last one. you'll find the same problem here and i used something like you do but with barycentric cs. when you are stuck with the positions of the children nodes, i don't know how to do using the tree library unfortunatelly. it would be cool if we could build the tree from the roots, instead of the head... does someone know how to do so? – sylcha Jun 09 '12 at 01:00
  • I think your ultimate goal might be doable but this one is more difficult... Can you give an example of the tree you want to grow? – percusse Jun 10 '12 at 12:10
  • @percusse I have edited my question to describe my ultimate goal. Hopefully it is indeed doable. Thanks for your help. – Henry DeYoung Jun 20 '12 at 21:00
  • So the level 2 nodes should be aligned with names and the level 1 nodes should be in the middle of the two names vertically, am I right? – percusse Jun 20 '12 at 22:30
  • @percusse Yes, that's right. In addition, I want the level 2 nodes to, in a sense, be the names themselves; that is, the edges from level 1 to level 2 should appear to connect to (ballot counts-1-2.east) etc. while keeping the level 1 nodes in the middle of the two names vertically. Thanks! – Henry DeYoung Jun 20 '12 at 22:40
  • Looks like the other way around is already an answer, doesn't it? I mean growing the tree and putting nodes left of each sibling :) But I'll try the hard way too. :) – percusse Jun 20 '12 at 22:43
  • @percusse I'm not sure understand that I what you mean by "growing the tree and putting nodes left of each sibling". So, when you have time to write up an answer, if you could expand on that comment, that'd be great. Of course, I'm also interested to know if you know of a better "hard way". :) – Henry DeYoung Jun 20 '12 at 22:56
  • @HenryDeYoung Indeed that was more or less what I have in mind. Nice approach! I have some time now and I'll try to understand the drawbacks. The length measurement is not difficult so that's what I'll be doing but I don't exactly understand the purpose of the window-like rectangle. Care to give me some details? – percusse Jun 21 '12 at 21:20
  • @percusse The window pane is used to locate actual images in separate nodes, such as with \node [image, img=ballotbob] at (ballot box-1-1) {};. The images have to be in separate nodes because they will be animated moving into the image placeholder position in the appropriate leaf of the tree. So, really, I'm using \node [image, img=ballotbob] at ($(ballot box-1-1)!\loc!(Bob's count-1-1)$) {};, where \loc increases from 0 to 1 during the animation. I wanted to leave the window-like ballot box in my question to hint at why I used a 4-by-2 matrix for ballot counts originally. – Henry DeYoung Jun 21 '12 at 21:42
  • OK one thing I noticed is that the length calculation that you do is slightly redundant since it's 0.5*(7+0.3333) em which are known and set by you. 0.5 is the node scaling. Do you want to keep it as it is? – percusse Jun 21 '12 at 22:45
  • @percusse That's a good point; I hadn't noticed that. (I'm new at this, in case you couldn't tell. :) Yes, I'll make that simplification. But, in general, I'd still be interested to know if there is a way to retroactively pass a measurement back to sibling distance. Also, there is still drawback #2 above, but, as I said, perhaps I just need to learn to be less greedy. – Henry DeYoung Jun 22 '12 at 01:13

2 Answers2

2

Here's a Forest version (rather late in the day, obviously), though I may not have understood the question since my solution doesn't look anything like Tom Bombadil's answer.

This just uses Forest, fit to and fit.

\documentclass[border=10pt]{standalone}
\usepackage{forest}
\begin{document}
\begin{forest}
  box around/.style={
    tikz+={
      \node [draw, inner sep=2.5pt, fit to=#1] {};
    },
  },
  box around nodes/.style={
    tikz+={
      \node [draw, inner sep=2.5pt, fit=#1] {};
    },
  },
  before typesetting nodes={
    for tree={
      grow=180,
      child anchor=parent,
      draw,
      s sep'=5pt,
    },
  },
  where n children=0{
    delay={
      content+=: $0$,
      append={[, before computing xy={l'=0pt}, anchor=east, calign with current edge, text height=10pt, text depth=10pt, text width=5pt, no edge]}
    },
    anchor=mid west,
    tier=voters,
    box around=tree,
  }{},
  [root
  [left, box around nodes=(!1) (!rF) (!s1) (!rL)
      [Alice]
      [Bob]
    ]
    [right, box around=descendants
      [Charlie]
      [Dave]
    ]
  ]
  \node (rb) [draw, inner sep=2.5pt, fit=(!r) (!r |- !rF.north) (!r |- !rL.south), inner xsep=7.5pt] {};
  \draw (!r) \foreach \i in {north,south} {edge (!r |- rb.\i)}  \foreach \i in {east,west} {edge (!r -| rb.\i)} ;
  \node [draw, fit=(rb), inner sep=2.5pt] {};
  \node [draw, inner sep=5pt, fit=(!rF.north west) (!rL.south east) (!r21.east)] {};
\end{forest}
\end{document}

Forest version

cfr
  • 198,882
2

You can name nodes and then use these names for relative positioning. You can also use absolute position and do all the stuff you usually do with nodes. In the example, the automatically positiones node is red, the relative ones are blue:

\documentclass[parskip]{scrartcl}
\usepackage[margin=15mm]{geometry}
\usepackage{tikz}
\usetikzlibrary{trees,fit,shapes,calc}

\begin{document}

\begin{tikzpicture} 
\tikzstyle{level 1}=[sibling distance=40mm] 
\tikzstyle{level 2}=[sibling distance=20mm] 
\tikzstyle{level 3}=[sibling distance=10mm] 
 \node (S) {S} 
      child{node {N}  child{node (M1) {mary}} }
      child{node {VP}     
            child{node[fill=red,circle,text=green] (V) {V} child{node{brought }}}
            child{node[fill=blue,text=yellow,circle] (NP2) at ($(V)+(2,-2)$) {NP}  child{node{D} child{node (A2){a}}} child{node{N} child{node (C2) {cat}}}  }
            child{node[fill=blue,text=yellow,circle] (PP) at ($(NP2)+(4,1)$) {PP}  child{node{IN} child{node{to}}}     child{node{N}  child{node{school}}} }                          }
;
\end{tikzpicture}

\end{document}

enter image description here

Tom Bombadil
  • 40,123