3

I'm trying to adapt this forest-based hierarchial diagram to construct my own taxonomy. This is a first try

\documentclass[border=1pt]{standalone}

\usepackage{forest}
\usetikzlibrary{arrows.meta, shapes.geometric, calc, shadows}

\colorlet{mygreen}{green!75!black}
\colorlet{col1in}{red!30}
\colorlet{col1out}{red!40}
\colorlet{col2in}{mygreen!40}
\colorlet{col2out}{mygreen!50}
\colorlet{col3in}{blue!30}
\colorlet{col3out}{blue!40}
\colorlet{col4in}{mygreen!20}
\colorlet{col4out}{mygreen!30}
\colorlet{col5in}{blue!10}
\colorlet{col5out}{blue!20}
\colorlet{col6in}{blue!20}
\colorlet{col6out}{blue!30}
\colorlet{col7out}{orange}
\colorlet{col7in}{orange!50}
\colorlet{col8out}{orange!40}
\colorlet{col8in}{orange!20}
\colorlet{linecol}{blue!60}

\begin{document}

\colorlet{mygreen}{green!75!black}
\colorlet{col1in}{red!30}
\colorlet{col1out}{red!40}
\colorlet{col2in}{mygreen!40}
\colorlet{col2out}{mygreen!50}
\colorlet{col3in}{blue!30}
\colorlet{col3out}{blue!40}
\colorlet{col4in}{mygreen!20}
\colorlet{col4out}{mygreen!30}
\colorlet{col5in}{blue!10}
\colorlet{col5out}{blue!20}
\colorlet{col6in}{blue!20}
\colorlet{col6out}{blue!30}
\colorlet{col7out}{orange}
\colorlet{col7in}{orange!50}
\colorlet{col8out}{orange!40}
\colorlet{col8in}{orange!20}
\colorlet{linecol}{blue!60}

\pgfkeys{/forest,
  rect/.append style={rectangle, rounded corners=2pt, inner color=col6in, outer color=col6out},
  ellip/.append style={ellipse, inner color=col5in, outer color=col5out},
  orect/.append style={rect, font=\sffamily\bfseries\LARGE, text width=325pt, text centered, minimum height=10pt, outer color=col7out, inner color=col7in},
  oellip/.append style={ellip, inner color=col8in, outer color=col8out, font=\sffamily\bfseries\large, text centered},
}
\begin{forest}
  for tree={
      font=\sffamily\bfseries,
      line width=1pt,
      draw=linecol,
      ellip,
      align=center,
      child anchor=north,
      parent anchor=south,
      drop shadow,
      l sep+=12.5pt,
      edge path={
        \noexpand\path[color=linecol, rounded corners=5pt, >={Stealth[length=10pt]}, line width=1pt, ->, \forestoption{edge}]
          (!u.parent anchor) -- +(0,-5pt) -|
          (.child anchor)\forestoption{edge label};
        },
      where level={3}{tier=tier3}{},
      where level={0}{l sep-=15pt}{},
      where level={1}{
        if n={1}{
          edge path={
            \noexpand\path[color=linecol, rounded corners=5pt, >={Stealth[length=10pt]}, line width=1pt, ->, \forestoption{edge}]
              (!u.west) -| (.child anchor)\forestoption{edge label};
            },
        }{
          edge path={
            \noexpand\path[color=linecol, rounded corners=5pt, >={Stealth[length=10pt]}, line width=1pt, ->, \forestoption{edge}]
              (!u.east) -| (.child anchor)\forestoption{edge label};
            },
        }
      }{},
  }
  [Break Causes, inner color=col1in, outer color=col1out
    [Locators, inner color=col2in, outer color=col2out
      [Structure-Based\\Locators, inner color=col4in, outer color=col4out
        [Hierarchy-based locator\\Target Not Found]
        [, phantom, calign with current]
        [Hierarchy-based locator\\Target Not Found]
      ]
      [Attribute-based\\Locators, inner color=col4in, outer color=col4out
        [Element Text\\Not Found]
        [, phantom, calign with current]
        [Element\\Attribute\\Not Found
          [Id attribute\\Not Found]
          [Href attribute\\Not Found]
          [Alt attribute\\Not Found]
          [Name attribute\\Not Found]
          [Type attribute\\Not Found]
          [Value attribute\\Not Found]
          [Class attribute\\Not Found]
          [Click attribute\\Not Found]
        ]
      ]
    ]
    [Values/Actions, inner color=col3in, outer color=col3out
      [Invalid Text Field\\Value Input]
      [Missing Value]
      [Value Deleted\\from Dropdown List]
      [, phantom, calign with current]
      [Unexpected\\Assertion Value]
      [Exceeding Action]
      [Modified Statement]
    ]
    [Page Reloading, inner color=col3in, outer color=col3out
      [Page Reload\\Needed]
      [, phantom, calign with current]
      [Page Reload\\no longer\\Needed]
    ]
    [JavaScript Popup\\Boxes, inner color=col3in, outer color=col3out
      [User Session\\Made Longer]
      [, phantom, calign with current]
      [User Session\\Made Shorter]
    ]
  ]
%  \begin{scope}[color = linecol, rounded corners = 5pt,
%    >={Stealth[length=10pt]}, line width=1pt, ->]
%    %\draw (sse2.south) -- (us.north -| sse2.south);
%    %\draw (sse3.south) -- (us.north -| sse3.south);
%    %\coordinate (c1) at ($(sse1.south)!2/5!(sse2.south)$);
%    %\coordinate (c2) at ($(sse3.south)!2/5!(sse4.south)$);
%    %\draw (sse1.south) -- +(0,-10pt) -| (us.north -| c1);
%    %\draw (sse4.south) -- +(0,-10pt) -| (us.north -| c2);
%  \end{scope}
\end{forest}
\end{document}

which produces the following output: enter image description here

However, due to the presence of many leaves, the layout is very small and unreadable. Thus, I've tried to change the grow orientation with

grow' = east,
child anchor=west,
parent anchor=east,

which produces this better result:

enter image description here

I'd like to further enhance it with: 1) better and clean edges layout, and 2) for each edge, two labels: one above and another below

Any hints do to so on my MWE? Is perhaps forest too tricky to build this kind of diagrams?

  • Please note that it is rather rude not to attribute code you borrow and that a link to the original makes it easier for other people trying to help you, too. – cfr Dec 1 '16 at 1:19
3

To get the arrows right you must turn them manually. As they are now they first start down 5pt followed by -| to the child node. Change it to right 5pt followed by |-. The second problem is that the ellipses are very unequal in width. So I set them to minimum width=60mm to line them up better.

\documentclass[border=1pt]{standalone}

\usepackage{forest}
\usetikzlibrary{arrows.meta, shapes.geometric, calc, shadows}

\colorlet{mygreen}{green!75!black}
\colorlet{col1in}{red!30}
\colorlet{col1out}{red!40}
\colorlet{col2in}{mygreen!40}
\colorlet{col2out}{mygreen!50}
\colorlet{col3in}{blue!30}
\colorlet{col3out}{blue!40}
\colorlet{col4in}{mygreen!20}
\colorlet{col4out}{mygreen!30}
\colorlet{col5in}{blue!10}
\colorlet{col5out}{blue!20}
\colorlet{col6in}{blue!20}
\colorlet{col6out}{blue!30}
\colorlet{col7out}{orange}
\colorlet{col7in}{orange!50}
\colorlet{col8out}{orange!40}
\colorlet{col8in}{orange!20}
\colorlet{linecol}{blue!60}

\begin{document}

\colorlet{mygreen}{green!75!black}
\colorlet{col1in}{red!30}
\colorlet{col1out}{red!40}
\colorlet{col2in}{mygreen!40}
\colorlet{col2out}{mygreen!50}
\colorlet{col3in}{blue!30}
\colorlet{col3out}{blue!40}
\colorlet{col4in}{mygreen!20}
\colorlet{col4out}{mygreen!30}
\colorlet{col5in}{blue!10}
\colorlet{col5out}{blue!20}
\colorlet{col6in}{blue!20}
\colorlet{col6out}{blue!30}
\colorlet{col7out}{orange}
\colorlet{col7in}{orange!50}
\colorlet{col8out}{orange!40}
\colorlet{col8in}{orange!20}
\colorlet{linecol}{blue!60}

\pgfkeys{/forest,
  rect/.append style={rectangle, rounded corners=2pt, inner color=col6in, outer color=col6out},
  ellip/.append style={ellipse, inner color=col5in, outer color=col5out ,minimum width=60mm},
  orect/.append style={rect, font=\sffamily\bfseries\LARGE, text width=325pt, text centered, minimum height=10pt, outer color=col7out, inner color=col7in},
  oellip/.append style={ellip, inner color=col8in, outer color=col8out, font=\sffamily\bfseries\large, text centered},
}
\begin{forest}
  for tree={
    grow' = east,
    child anchor=west,
    parent anchor=east,
    font=\sffamily\bfseries,
      line width=1pt,
      draw=linecol,
      ellip,
      align=center,
      %child anchor=north,
      %parent anchor=south,
      drop shadow,
      l sep+=12.5pt,
      edge path={
        \noexpand\path[color=linecol, rounded corners=5pt, >={Stealth[length=10pt]}, line width=1pt, ->, \forestoption{edge}]
          (!u.parent anchor) -- +(5pt,0) |-
          (.child anchor)\forestoption{edge label};
        },
      where level={3}{tier=tier3}{},
      %where level={0}{l sep-=15pt}{},
      where level={1}{
        if n={1}{
          edge path={
            \noexpand\path[color=linecol, rounded corners=5pt, >={Stealth[length=10pt]}, line width=1pt, ->, \forestoption{edge}]
              (!u.east) -- +(5pt,0pt) |- (.child anchor)\forestoption{edge label};
            },
        }{
          edge path={
            \noexpand\path[color=linecol, rounded corners=5pt, >={Stealth[length=10pt]}, line width=1pt, ->, \forestoption{edge}]
              (!u.east)  -- +(5pt,0pt) |- (.child anchor)\forestoption{edge label};
            },
        }
      }{},
  }
  [Break Causes, inner color=col1in, outer color=col1out
    [Locators, inner color=col2in, outer color=col2out
      [Structure-Based\\Locators, inner color=col4in, outer color=col4out
        [Hierarchy-based locator\\Target Not Found]
        [, phantom, calign with current]
        [Hierarchy-based locator\\Target Not Found]
      ]
      [Attribute-based\\Locators, inner color=col4in, outer color=col4out
        [Element Text\\Not Found]
        [, phantom, calign with current]
        [Element Attribute\\Not Found
          [Id attribute\\Not Found]
          [Href attribute\\Not Found]
          [Alt attribute\\Not Found]
          [Name attribute\\Not Found]
          [Type attribute\\Not Found]
          [Value attribute\\Not Found]
          [Class attribute\\Not Found]
          [Click attribute\\Not Found]
        ]
      ]
    ]
    [Values/Actions, inner color=col3in, outer color=col3out
      [Invalid Text Field\\Value Input]
      [Missing Value]
      [Value Deleted\\from Dropdown List]
      [, phantom, calign with current]
      [Unexpected\\Assertion Value]
      [Exceeding Action]
      [Modified Statement]
    ]
    [Page Reloading, inner color=col3in, outer color=col3out
      [Page Reload\\Needed]
      [, phantom, calign with current]
      [Page Reload\\no longer\\Needed]
    ]
    [JavaScript Popup\\Boxes, inner color=col3in, outer color=col3out
      [User Session\\Made Longer]
      [, phantom, calign with current]
      [User Session\\Made Shorter]
    ]
  ]
%  \begin{scope}[color = linecol, rounded corners = 5pt,
%    >={Stealth[length=10pt]}, line width=1pt, ->]
%    %\draw (sse2.south) -- (us.north -| sse2.south);
%    %\draw (sse3.south) -- (us.north -| sse3.south);
%    %\coordinate (c1) at ($(sse1.south)!2/5!(sse2.south)$);
%    %\coordinate (c2) at ($(sse3.south)!2/5!(sse4.south)$);
%    %\draw (sse1.south) -- +(0,-10pt) -| (us.north -| c1);
%    %\draw (sse4.south) -- +(0,-10pt) -| (us.north -| c2);
%  \end{scope}
\end{forest}
\end{document}

enter image description here

I have not looked at how to label the edges. To do that you should probably scale down the example a bit.

| improve this answer | |
  • Thank you Stefan, looks must better now and it's converging on what I'm looking for! – tsigalko18 Nov 30 '16 at 14:44
  • Labelling the edges was pretty easy: I've added to each node edge label={node[below] {0.00\%} node[above] {0.00\%}} – tsigalko18 Nov 30 '16 at 15:05
  • 1
    I think it makes much more sense to use forked edges now the edges library is available @tsigalko18. This kind of manual coding for this was required for version 1 of Forest but really isn't now. Moreover, forked edges will adapt to the direction of growth, which should help avoid the problems seen by the OP when changing that. – cfr Dec 1 '16 at 3:08
  • I agree with you, I'm new to this community and it was absolutely unintentional. – tsigalko18 Dec 3 '16 at 7:41
4

The current version of Forest makes this type of tree a whole lot easier. I recommend making the most of it.

The ellipse shape is not a good choice here. Either use rectangles, perhaps with rounded corners, or use rounded rectangle. Otherwise, the shape is just eating space and makes the tree look a bit odd besides.

\tikzset{%
  ellip/.append style={rounded rectangle, ...},
}

[ellip is, of course, now misnamed, but TeX won't care.]

Forest now has an edges library which provides forked edges. This means we don't have to worry about the direction of the tree's growth: the change of direction in the branches is automatically adapted.

  forked edges,

The phantom nodes are not doing much except adding unsightly vertical spacing in the wrong places. I'd get rid of them. However, I've put some code in to add them automatically, should you wish to reinstate them. If a parent has an odd number of children, we align with the middle child. Otherwise, not. (There are no odd numbers of children in this tree, but there might be if you add or subtract one somewhere.)

  where={isodd(n_children())}{%
    for n/.wrap pgfmath arg={{#1}{calign with current edge}}{int((n_children()+1)/2)}
  }{% if you really want the phantoms in when there is an even number of children, uncomment this
%     if n children=0{}{%
%       delay={%
%         for n/.wrap pgfmath arg={{#1}{%
%             insert after={[, phantom, calign with current edge]},
%           }}{int((n_children())/2)},
%       }
%     },
  },

I would, however, use one phantom in the root's children to avoid strange looking edges. Alternatively, drop the rounded corners. (But I quite like them, so I've added the phantom.)

    ]
    [, phantom, calign with current edge]
    [Values/Actions, ellip=3

Forest can't quite place the nodes correctly without a bit of help. I've increased l sep a little, set anchor=parent and aligned all nodes in level 2 to handle this.

    for tree={%
      ...
      anchor=parent,
      l sep'+=5pt,
      ...
    },
    ...
    where level=2{tier=second}{},

Note that you do not need to increase l sep if you don't use arrows on the edges. I've no idea whether you really need these or not. I'd probably drop them, but here I've just made them their normal size rather than extra large.

I've removed unused styles and colours, repeated definitions and simplified the way the colouring is handled so that the tree specification is a bit cleaner.

\tikzset{%
  ellip/.append style={rounded rectangle, inner color=col#1in, outer color=col#1out},
}

allows us to write e.g. ellip=5 for inner and outer colouring with col5in and col5out respectively.

Here's the initial result:

initial result

Initial code:

\documentclass[tikz,border=10pt]{standalone}
\usepackage[edges]{forest}
\usetikzlibrary{arrows.meta,shapes.geometric,shadows}
\begin{document}

\colorlet{mygreen}{green!75!black}
\colorlet{col1in}{red!30}
\colorlet{col1out}{red!40}
\colorlet{col2in}{mygreen!40}
\colorlet{col2out}{mygreen!50}
\colorlet{col3in}{blue!30}
\colorlet{col3out}{blue!40}
\colorlet{col4in}{mygreen!20}
\colorlet{col4out}{mygreen!30}
\colorlet{col5in}{blue!10}
\colorlet{col5out}{blue!20}
\colorlet{linecol}{blue!60}

\tikzset{%
  ellip/.append style={rounded rectangle, inner color=col#1in, outer color=col#1out},
}
\begin{forest}
  forked edges,
  for tree={
      font=\sffamily\bfseries,
      line width=1pt,
      draw=linecol,
      ellip=5,
      align=center,
      grow'=0,
      edge+={color=linecol, line width=1pt, rounded corners=5pt, -Stealth},
      drop shadow,
      anchor=parent,
      l sep'+=5pt,
  },
  where={isodd(n_children())}{%
    for n/.wrap pgfmath arg={{#1}{calign with current edge}}{int((n_children()+1)/2)}
  }{% if you really want the phantoms in when there is an even number of children, uncomment this
%     if n children=0{}{%
%       delay={%
%         for n/.wrap pgfmath arg={{#1}{%
%             insert after={[, phantom, calign with current edge]},
%           }}{int((n_children())/2)},
%       }
%     },
  },
  where level=2{tier=second}{},
  [Break Causes, ellip=1
    [Locators, ellip=2
      [Structure-Based\\Locators, ellip=4
        [Hierarchy-based locator\\Target Not Found]
        [Hierarchy-based locator\\Target Not Found]
      ]
      [Attribute-based\\Locators, ellip=4
        [Element\\Text\\Not Found]
        [Element\\Attribute\\Not Found
          [Id attribute\\Not Found]
          [Href attribute\\Not Found]
          [Alt attribute\\Not Found]
          [Name attribute\\Not Found]
          [Type attribute\\Not Found]
          [Value attribute\\Not Found]
          [Class attribute\\Not Found]
          [Click attribute\\Not Found]
        ]
      ]
    ]
    [, phantom, calign with current edge]
    [Values/Actions, ellip=3
      [Invalid Text Field\\Value Input]
      [Missing Value]
      [Value Deleted\\from Dropdown List]
      [Unexpected\\Assertion Value]
      [Exceeding Action]
      [Modified Statement]
    ]
    [Page Reloading, ellip=3
      [Page Reload\\Needed]
      [Page Reload\\no longer\\Needed]
    ]
    [JavaScript Popup\\Boxes, ellip=3
      [User Session\\Made Longer]
      [User Session\\Made Shorter]
    ]
  ]
\end{forest}
\end{document}

If you need labels on the edges, I assume you want them on the horizontal parts. If so, I would definitely drop the arrows unless you can make these a lot longer.

I would do this by minimising typing. I would set things up so that I could put the labels into the node itself and have Forest split the content when formatting the tree. I gather you may want percentages to 2dp, so I've used that as an example.

We add some styles to start.

\tikzset{%
  ...
  label look/.style={font=\scriptsize},

Style for the labels. Now the number format.

  /pgf/number format/.cd,
  fixed,
  fixed zerofill,
  precision=2,
}

We want a couple of Forest options to hold the labels.

\forestset{%
  declare toks={alabel}{0},
  declare toks={blabel}{0},
}

Now we add the code to split and format the labels.

  before typesetting nodes={%
    for tree={%
      split option={content}{:}{content,alabel,blabel},
      edge label/.wrap 2 pgfmath args={node [above, label look, anchor=south east] {\pgfmathprintnumber{#1}\%} node [below, label look, anchor=north east] {\pgfmathprintnumber{#2}\%}}{alabel()}{blabel()}
    },
  },

This requires increasing l sep a bit to make room and I added s sep'+=10pt to Locators to avoid nodes overlapping each other. Forest loses track a bit, I think. (The labels it doesn't try to track, but the nodes it does.)

Then we can write, say,

 Structure-Based\\Locators:9:4.658

for a node with 9.00% in a label above and 4.66% below and content of Structure-Based\\Locators.

Here's the result:

labelled tree

Complete code:

\documentclass[tikz,border=10pt]{standalone}
\usepackage[edges]{forest}
\usetikzlibrary{arrows.meta,shapes.geometric,shadows}
\begin{document}

\colorlet{mygreen}{green!75!black}
\colorlet{col1in}{red!30}
\colorlet{col1out}{red!40}
\colorlet{col2in}{mygreen!40}
\colorlet{col2out}{mygreen!50}
\colorlet{col3in}{blue!30}
\colorlet{col3out}{blue!40}
\colorlet{col4in}{mygreen!20}
\colorlet{col4out}{mygreen!30}
\colorlet{col5in}{blue!10}
\colorlet{col5out}{blue!20}
\colorlet{linecol}{blue!60}

\tikzset{%
  ellip/.append style={rounded rectangle, inner color=col#1in, outer color=col#1out},
  label look/.style={font=\scriptsize},
  /pgf/number format/.cd,
  fixed,
  fixed zerofill,
  precision=2,
}
\forestset{%
  declare toks={alabel}{0},
  declare toks={blabel}{0},
}
\begin{forest}
  forked edges,
  for tree={%
      font=\sffamily\bfseries,
      line width=1pt,
      draw=linecol,
      ellip=5,
      align=center,
      grow'=0,
      edge+={color=linecol, line width=1pt, rounded corners=5pt},
      drop shadow,
      anchor=parent,
      l sep'+=25pt,
  },
  where={isodd(n_children())}{%
    for n/.wrap pgfmath arg={{#1}{calign with current edge}}{int((n_children()+1)/2)}
  }{},
  where level=2{tier=second}{},
  before typesetting nodes={%
    for tree={%
      split option={content}{:}{content,alabel,blabel},
      edge label/.wrap 2 pgfmath args={node [above, label look, anchor=south east] {\pgfmathprintnumber{#1}\%} node [below, label look, anchor=north east] {\pgfmathprintnumber{#2}\%}}{alabel()}{blabel()}
    },
  },
  [Break Causes, ellip=1
    [Locators:5.76, ellip=2, s sep'+=10pt
      [Structure-Based\\Locators:9:4.658, ellip=4
        [Hierarchy-based locator\\Target Not Found:82.342:45]
        [Hierarchy-based locator\\Target Not Found:3:5.68]
      ]
      [Attribute-based\\Locators:98.3:4, ellip=4
        [Element\\Text\\Not Found:98.34:23.1]
        [Element\\Attribute\\Not Found:46.98:9
          [Id attribute\\Not Found:34:53.2345]
          [Href attribute\\Not Found:98:43.2335]
          [Alt attribute\\Not Found:78:9]
          [Name attribute\\Not Found:0.987:23]
          [Type attribute\\Not Found:11:70]
          [Value attribute\\Not Found:8:2]
          [Class attribute\\Not Found:1:6]
          [Click attribute\\Not Found:8:5]
        ]
      ]
    ]
    [, phantom, calign with current edge]
    [Values/Actions:29.8:76.2, ellip=3
      [Invalid Text Field\\Value Input:0.987:23]
      [Missing Value:98.3:4]
      [Value Deleted\\from Dropdown List:86:34]
      [Unexpected\\Assertion Value:11:70]
      [Exceeding Action:5.76]
      [Modified Statement:0.987:23]
    ]
    [Page Reloading:98.3:4, ellip=3
      [Page Reload\\Needed:34:53.2345]
      [Page Reload\\no longer\\Needed:96:48]
    ]
    [JavaScript Popup\\Boxes:5.87:56.9, ellip=3
      [User Session\\Made Longer:34:53.2345]
      [User Session\\Made Shorter:11:70]
    ]
  ]
\end{forest}
\end{document}
| improve this answer | |
  • Thank you, very nice solution and hints. The new version of Forest makes the code much cleaner. – tsigalko18 Dec 3 '16 at 7:43

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.