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

1 Answer 1

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.

12
  • 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. Mar 2, 2017 at 20:40
  • What does multi do in the document class? Mar 2, 2017 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, 2017 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, 2017 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.) Mar 3, 2017 at 9:56

You must log in to answer this question.

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