4

I am making a phylogenetic tree with forest, using this question and answer as a guide.

I would like to have a vertical axis for time, as shown in the image below but I have two related problems that I do not know how to address.

  1. I would like to have the axis appear to the left of the tree, offset by a specific amount, say 1 cm to the left of the left-most branch (or leaf node label; e.g., gray fox in this example). Right, now, I have to find the distance by trial and error. As the size of tree changes (adding or removing nodes), the axis position should adjust accordingly.

  2. I would like the length of the axis to automatically fit the height of the tree. Right now, I manually count the number of levels and multiply by the value of l set (1 cm in this case).

Can both of these be determined programmatically?

\documentclass{article}
\usepackage{forest}
\usetikzlibrary{positioning, backgrounds}
\tikzset{
    timeline/.style={align=right, text width=2cm},
     no edge from this parent/.style={
        every child/.append style={
        edge from parent/.style={draw=none}}},
}
\forestset{
    every leaf node/.style={
        if n children=0{\raggedright #1}{}
    },
    every tree node/.style={
        if n children=0{}{#1}
    },
    mytree/.style={
        for tree={
            edge path={
            \noexpand\path [draw, ultra thick, \forestoption{edge}] (!u.parent anchor) -| (.child anchor)\forestoption{edge label};
            },
            every tree node={draw=none,inner sep=0, outer sep=0, minimum size=0},
            every leaf node/.style={align=right},
            grow'=north,
            parent anchor=north, 
            child anchor=south,
            anchor=base,
            l sep=1cm,
            s sep=3mm,
            draw=none,
                if n children=0{tier=word, align=center, base=bottom}{}
        }
    }
}

\begin{document}
\begin{forest} mytree
    [, name=lvl0
        [,name=lvl1
            [,name=lvl2
                [gray fox]
                [island fox]
            ]
            [
                [,name=lvl3
                    [black\\bear]
                    [giant\\panda] 
                ]
                [
                    [northern\\elephant\\seal]
                    [walrus] 
                ] 
            ] 
        ]
    ]
    %
    \coordinate (tml) at (-6.5,0);
    \begin{scope}[on background layer]
        \draw (-5.3,0) -- (-5.3,4) node [above] {\textsc{mya}};
        \foreach \x/\age/\len in {1/10/2, 2/7.4/4, 3/4/4}
            {
            \node[timeline] (age\x) at (lvl\x -| tml) {\age};
            \draw[gray, thick, dotted] (age\x) -- (lvl\x);
        }
    \end{scope}
\end{forest}
\end{document}

enter image description here

4

Something like this?

\documentclass[border=10pt,multi,tikz]{standalone}
\usepackage{forest}
\usetikzlibrary{backgrounds}
\forestset{
  declare dimen register={timeline offset},
  timeline offset'=10mm,
  declare toks register={timeline target},
  timeline target=,
  declare boolean={tree node}{0},
  mytree/.style={
    for tree={
      edge+={ultra thick},
      edge path'={
        (!u.parent anchor) -| (.child anchor)
      },
      grow'=north,
      parent anchor=children,
      child anchor=parent,
      anchor=base,
      l sep=1cm,
      s sep=3mm,
      if n children=0{tier=word, align=center, base=bottom, not tree node}{coordinate, tree node}
    }
  },
  timeline/.style={
    before drawing tree={
      timeline target/.option=name,
      tempdima/.option=y,
      for tree={
        if={>OR>{y}{tempdima}}{timeline target/.option=name}{},
      }
    },
    tikz+={
      \begin{scope}[on background layer]
        \draw ([xshift=-\foresteregister{timeline offset}]current bounding box.west |- .parent anchor) coordinate (timeline base) -- (\foresteregister{timeline target}.child anchor -| timeline base) node [above] {\textsc{mya}};
      \end{scope}
    },
  },
}

\begin{document}
\begin{forest}
  mytree,
  timeline
    [, name=lvl0
        [,name=lvl1
            [,name=lvl2
                [gray fox]
                [island fox]
            ]
            [
                [,name=lvl3
                    [black\\bear]
                    [giant\\panda]
                ]
                [
                    [northern\\elephant\\seal]
                    [walrus]
                ]
            ]
        ]
    ]
    \begin{scope}[on background layer]
        \foreach \x/\age/\len in {1/10/2, 2/7.4/4, 3/4/4}
            {
            \node [anchor=east] (age\x) at (lvl\x -| timeline base) {\age};
            \draw [gray, thick, dotted] (age\x) -- (lvl\x);
        }
    \end{scope}
\end{forest}
\end{document}

timeline automatically

EDIT

Here's a somewhat different version, which lets you say age=<value> for nodes in the tree and automatically does the rest. At least, that's the theory.

\documentclass[border=10pt,multi,tikz]{standalone}
\usepackage{forest}
\usetikzlibrary{backgrounds}
\forestset{
  declare dimen register={timeline offset},
  timeline offset'=10mm,
  declare toks register={timeline target},
  timeline target=,
  declare boolean={tree node}{0},
  age/.code={
    \pgfkeys{/level age \foresteoption{level}/.initial={#1}}
  },
  mytree/.style={
    for tree={
      edge+={ultra thick},
      edge path'={
        (!u.parent anchor) -| (.child anchor)
      },
      grow'=north,
      parent anchor=children,
      child anchor=parent,
      anchor=base,
      l sep=1cm,
      s sep=3mm,
      if n children=0{tier=word, align=center, base=bottom, not tree node}{coordinate, tree node}
    }
  },
  timeline/.style={
    before drawing tree={
      timeline target/.option=name,
      tempdima/.option=y,
      for tree={
        if={>OR>{y}{tempdima}}{timeline target/.option=name}{},
      },
      tempcounta/.option=level,
      for nodewalk={
        filter={tree breadth-first}{>On>{n children}{0}}%
      }{
        if={
          >OR>{level}{tempcounta}%
        }{
          tempcounta/.option=level,
          tikz+={
            \edef\tempa{level age \foresteoption{level}}
            \begin{scope}[on background layer]
              \pgfkeys{/level age \foresteoption{level}/.get=\mylevelage}
              \draw [draw=gray, thick, dotted] () -- ( -| timeline base) node [left, anchor=east] {\mylevelage};
            \end{scope}
          }%
        }{}
      },
    },
    tikz+={
      \begin{scope}[on background layer]
        \draw ([xshift=-\foresteregister{timeline offset}]current bounding box.west |- .parent anchor) coordinate (timeline base) -- (\foresteregister{timeline target}.child anchor -| timeline base) node [above] {\textsc{mya}};
      \end{scope}
    },
  },
}

\begin{document}
\begin{forest}
  mytree,
  timeline
  [
    [, age=10
      [
        [gray fox]
        [island fox, age=4]
      ]
      [, age=7.4
        [
          [black\\bear]
          [giant\\panda]
        ]
        [
          [northern\\elephant\\seal]
          [walrus]
        ]
      ]
    ]
  ]
\end{forest}
\end{document}

Output as above.

  • I get the following error: Package PGF Math Error: Unknown operator '0' or 'OR' (in '>OR>{y}{tempdima}'). I'm compiling with TeXShop with texlive 2016. – Michael S Taylor Mar 2 '17 at 20:40
  • What does multi do in the document class? – Michael S Taylor Mar 2 '17 at 20:41
  • @MikeTaylor It doesn't matter here, but it can be helpful if you have multiple images in the same file. – cfr Mar 2 '17 at 22:59
  • @MikeTaylor You need to update TeX Live using TeX Live Update Utility. You have an outdated copy of Forest. – cfr Mar 2 '17 at 23:00
  • 1
    Another possible simplification: define style age/.style={tikz+={\begin{scope}[on background layer] \draw [gray, thick, dotted] () -- ( -| timeline base) node[black,anchor=east]{#1}; \end{scope} }} and use it in place of the original name=lvlX with the appropriate age. (Setting names lvlX is then unnecessary. The drawback is that you need to manually find the relevant nodes where the age should be called -- but this was the case in the guiding answer as well.) – Sašo Živanović Mar 3 '17 at 9:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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