1

I am constructing a phylogenetic tree that started with this question. Each node has an associated age. I would like to scale the branch length based on the age difference between two successive nodes.

I've figured out how to do this for each branch by subtracting the younger age from the older age, and scaling by a factor (see MWE). This requires me to enter the age once for the timeline and once for the branch length. If I need to adjust the age, then I have to remember to adjust it twice.

Question 1: Is there a way to scale the branch length to time automatically by subtracting the age of one node from the age of the previous node?

Notice too in the example figure below that ages close together overlap. For this and other reasons, I would like to have the option of printing any age along the time scale on the left.

I declared a boolean called print age that would print the age and dotted line to the node if the print age=1 for a given node. All I've managed to accomplish is printing all or no ages, depending on whether the print age is set to 0 or 1 when first declared.

Question 2: How can I use the boolean to print only selected ages? For this question, here is what I think I need to accomplish my goals:

A. I decide whether to show the age along the time scale.

B. If the age is shown, decide whether the dotted line is present or absent (or whether the color is gray or white). This allows me to have selected ages along the time scale, not print an age that will overprint (e.g., 290 and 305 in the example), and show only dotted lines to highlight nodes of interest.

\documentclass[border=10pt]{article}
\usepackage{tikz}
\usetikzlibrary{backgrounds, positioning, arrows, arrows.meta}
\usepackage{forest}
\forestset{
  declare toks register={timeline scale},
  timeline scale=0.2,
  declare boolean={print age}{0}, % Default to no ages printed
  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+={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}
    }
  },
%
  age/.style={
% if print age={ %This prints all or no ages depending on declared boolean value above.
    tikz+={
      \begin{scope}[on background layer] 
            \draw [gray, thick, dotted] () -- ( -| timeline base) node[black,anchor=east]{#1}; 
      \end{scope} 
    }
%   }{} end if print age
  },
%
  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] { extsc{mya}};
      \end{scope}
    },
  },
}

\begin{document}
\begin{forest}
  mytree,
  timeline
    [, name=ancestor, age=454,
        [, age=354, l+={\forestregister{timeline scale}*(454-354)}
      [fishes]
          [, age=325, l+={\forestregister{timeline scale}*(354-325)}, 
             [, age=305, l+={\forestregister{timeline scale}*(325-305)}
               [frogs]
              [salamanders]
             ]
              [,age=290,l+={\forestregister{timeline scale}*(325-290)}, print age=1 %Print the age for this node.
                [
                  [mammals]
                ]
                [,age=206, l+={\forestregister{timeline scale}*(290-206)}
                  [reptiles, l+={\forestregister{timeline scale}*(206)}] %Scale the top-most branches.
                  [birds] 
                ]
              ]
            ]
        ]
    ]
\end{forest}
\end{document}

enter image description here

  • You need to delay evaluation of the boolean for at least one cycle so that Forest uses the values set when parsing the tree specification. – cfr Mar 9 '17 at 0:36
1

Forest works in cycles. If you need to use values set in the tree specification, you need to delay by at least one cycle so that Forest has parsed the tree and set those values. Otherwise, it will just use the values which apply during the first cycle, which will be the defaults.

I would make age a count so that you can manipulate it and then use it when applying the format to the tree. You can then use it to print the age, set the level distance etc.

For some reason, I get a slightly shorter tree with my automated method, but setting the scale to 0.3 approximates the example in the question. (108mm rather than 109mm with 0.2 and the original code, according to Okular, which includes the border.)

\documentclass[border=10pt,tikz]{standalone}
\usetikzlibrary{backgrounds}
\usepackage{forest}
\forestset{
  declare toks register={timeline scale},
  timeline scale=0.2,
  declare boolean={print age}{0}, % Default to no ages printed
  declare dimen register={timeline offset},
  timeline offset'=10mm,
  declare toks register={timeline target},
  timeline target=,
  declare boolean={tree node}{0},
  declare count={age}{0},
  mytree/.style={
    for tree={
      edge+={thick},
      edge path'={
        (!u.parent anchor) -| (.child anchor)
      },
      grow=north,
      parent anchor=children,
      child anchor=parent,
      anchor=base,
      s sep=3mm,
      if n children=0{tier=word, align=center, base=bottom, not tree node}{coordinate, tree node}
    }
  },
  timeline/.style={
    before typesetting nodes={
      where level=0{}{
        if={
          > O_=! O_=! & {age}{0} {!u.age}{0}
        }{
          l+/.process={ ROOw3+n {timeline scale}{age}{!u.age}{##1*(##3-##2)} }
        }{},
        if print age={
          tikz+/.process={
            Ow{age}%
            {
              \begin{scope}[on background layer]
                \draw [gray, thick, dotted] () -- ( -| timeline base) node[black,anchor=east]{##1};
              \end{scope}
            }%
          },
        }{},
      },
    },
    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] { extsc{mya}};
      \end{scope}
    },
  },
}

\begin{document}
\begin{forest}
  mytree,
  timeline,
  timeline scale=0.3
  [, name=ancestor, age=454,
    [, age=354,
      [fishes]
      [, age=325,
        [, age=305,
          [frogs]
          [salamanders]
        ]
        [, age=290, print age
          [
            [mammals]
          ]
          [, age=206,
            [reptiles, l+={\forestregister{timeline scale}*(206)}] %Scale the top-most branches.
            [birds]
          ]
        ]
      ]
    ]
  ]
\end{forest}
\end{document}

automated tree

  • Thank you. What is accomplished by > O+tt=! O+tt=!, ROOw3+n, and Ow{age}`? I assume these are related to the age counter but I am not following the logic involved. – Michael S Taylor Mar 10 '17 at 14:06
  • @MikeTaylor Thanks. The first is a mistake, which I've corrected above. That is, it works, but is completely unnecessary and makes no sense. It is because age was originally toks in your code, so I needed the t for a text comparison. But since age is now a count, that's pointless. The second is just used for the calculation: it takes the value of timeline scale, the age for the current node and the age for its parent, does the relevant subtraction/multiplication and adds the result to l. This is the part responsible for the autoscaling of the edges based on age differences. – cfr Mar 10 '17 at 20:08
  • @MikeTaylor The third is just picking up the value of age for use in the argument for tikz. It is used to print the correct label on the left, if applicable. Note that Ow{age} is not well-specified on its own. Ow{age}{...} is the complete thing. Similar for the other things you list: on their own, they will just give an error. They only make sense with the stuff after. ROOw3+n {timeline scale}{age}{!u.age}{##1*(##3-##2)} makes sense, but ROOw3+n on its own does not. It just specifies the kinds of things which will come next. – cfr Mar 10 '17 at 20:10
  • @MikeTaylor I'm not sure if I really answered your question .... – cfr Mar 10 '17 at 20:14

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.