Tree drawing (Qt)

Overview

ETE’s treeview extension provides a highly programmable drawing system to render any hierarchical tree structure as png, svg or pdf images.

Although several predefined visualization layouts are included with the default installation, custom styles can be easily created from scratch.

Image customization is performed through four elements:

  • TreeStyle, sets general options about the image (shape, rotation, etc.)

  • NodeStyle, defines the specific aspect of each node (size, color, background, line type, etc.)

  • faces, small pieces of extra graphical information that can be added to nodes (text labels, images, graphs, etc.)

  • layout functions, normal python functions that controls how node styles and faces are dynamically added to nodes

Images can be rendered using the render method, or interactively visualized using a built-in GUI invoked by the show method.

Interactive visualization of trees

ETE’s tree drawing engine is fully integrated with a built-in graphical user interface (GUI) which allows to explore and manipulate node’s properties and tree topology. To start the visualization of a node (tree or subtree), you can simply call the show method.

One of the advantages of this visualization is that you can use it to interrupt a given program/analysis, explore the tree, manipulate it, and continue with the execution. Note that changes made using the GUI will be kept after quiting the GUI. This feature is specially useful during python sessions, where analyses are performed interactively.

from ete4 import Tree
t = Tree('((a,b),c);')
t.show()

The GUI allows many operations to be performed graphically. However it does not implement all the possibilities of the programming toolkit.

Rendering trees as images

Tree images can be directly written as image files. Supported formats are png, svg, and pdf. Note that while png images are raster images, pdf and svg pictures are rendered as vector graphics, thus allowing their later modification and scaling.

To generate an image, the render method should be used instead of show. The only required argument is the file name, whose extension will determine the image format (png, svg, or pdf). Several parameters regarding the image size and resolution can be adjusted:

Argument

Description

units

px: pixels, mm: millimeters, in: inches

h

height of the image in units

w

width of the image in units

dpi

dots per inch

Note

If h and w values are both provided, image size will be adjusted even if it requires breaking the original aspect ratio of the image. If only one value (h or w) is provided, the other will be estimated to maintain the aspect ratio. If no sizing values are provided, the image will be adjusted to A4.

from ete4 import Tree
t = Tree('((a,b),c);')
t.render('mytree.png', w=183, units='mm')

Customizing the aspect of trees

Image customization is performed through four main elements: tree style, node style, faces, and layouts.

Tree style

The TreeStyle class can be used to create a custom set of options that control the general aspect of the tree image. Tree styles can be passed to the show and render methods. For instance, TreeStyle allows to modify the scale used to render tree branches or choose between circular or rectangular tree drawing modes.

from ete4 import Tree
from ete4.treeview import TreeStyle

t = Tree('((a,b),c);')
circular_style = TreeStyle()
circular_style.mode = 'c'  # draw tree in circular mode
circular_style.scale = 20
t.render('mytree.png', w=183, units='mm', tree_style=circular_style)

A number of parameters can be controlled through custom tree style objects. Check the TreeStyle documentation for a complete list of accepted values.

In the following, se show some common cases.

Show leaf node names, branch length and branch support

Automatically add node names and branch information to the tree image:

from ete4 import Tree
from ete4.treeview import TreeStyle
t = Tree()
t.populate(10, random_branches=True)
ts = TreeStyle()
ts.show_leaf_name = True
ts.show_branch_length = True
ts.show_branch_support = True
t.show(tree_style=ts)
../_images/show_info.png

Change branch length scale (zoom in x)

Increases the length of the tree by changing the scale:

from ete4 import Tree
from ete4.treeview import TreeStyle
t = Tree()
t.populate(10, random_branches=True)
ts = TreeStyle()
ts.show_leaf_name = True
ts.scale = 120  # 120 pixels per branch length unit
t.show(tree_style=ts)
../_images/scale_x.png

Change branch separation between nodes (zoom in y)

Increases the separation between leaf branches:

from ete4 import Tree
from ete4.treeview import TreeStyle
t = Tree()
t.populate(10, random_branches=True)
ts = TreeStyle()
ts.show_leaf_name = True
ts.branch_vertical_margin = 10  # 10 pixels between adjacent branches
t.show(tree_style=ts)
../_images/scale_y.png

Rotate a tree

Draws a rectangular tree from top to bottom:

from ete4 import Tree
from ete4.treeview import TreeStyle
t = Tree()
t.populate(10)
ts = TreeStyle()
ts.show_leaf_name = True
ts.rotation = 90
t.show(tree_style=ts)
../_images/rotated_tree.png

Circular tree in 180 degrees

Draws a circular tree using a semi-circumference:

from ete4 import Tree
from ete4.treeview import TreeStyle
t = Tree()
t.populate(30)
ts = TreeStyle()
ts.show_leaf_name = True
ts.mode = 'c'
ts.arc_start = -180  # 0 degrees = 3 o'clock
ts.arc_span = 180
t.show(tree_style=ts)
../_images/semi_circular_tree.png

Add legend and title

from ete4 import Tree
from ete4.treeview import TreeStyle, TextFace
t = Tree('((a,b),c);')
ts = TreeStyle()
ts.show_leaf_name = True
ts.title.add_face(TextFace('Hello ETE', fsize=20), column=0)
t.show(tree_style=ts)

Node style

Through the NodeStyle class the aspect of each single node can be controlled, including its size, color, background and branch type.

A node style can be defined statically and attached to several nodes.

Simple tree in which the same style is applied to all nodes:

from ete4 import Tree
from ete4.treeview import NodeStyle, TreeStyle

t = Tree('((a,b),c);')

# Basic tree style.
ts = TreeStyle()
ts.show_leaf_name = True

# Draw nodes as small red spheres of diameter equal to 10 pixels.
nstyle = NodeStyle()
nstyle['shape'] = 'sphere'
nstyle['size'] = 10
nstyle['fgcolor'] = 'darkred'

# Gray dashed branch lines
nstyle['hz_line_type'] = 1
nstyle['hz_line_color'] = '#cccccc'

# Apply the same static style to all nodes in the tree. Note that
# if 'nstyle' is modified, changes will affect all nodes.
for n in t.traverse():
    n.set_style(nstyle)

t.show(tree_style=ts)
../_images/node_style_red_nodes.png

If you want to draw nodes with different styles, an independent NodeStyle instance must be created for each node. Note that node styles can be modified at any moment by accessing the img_style attribute.

Simple tree in which the different styles are applied to each node:

from ete4 import Tree
from ete4.treeview import NodeStyle, TreeStyle
t = Tree('((a,b),c);')

# Basic tree style.
ts = TreeStyle()
ts.show_leaf_name = True

# Create an independent node style for each node, which is
# initialized with a red foreground color.
for n in t.traverse():
    nstyle = NodeStyle()
    nstyle['fgcolor'] = 'red'
    nstyle['size'] = 15
    n.set_style(nstyle)

# Let's now modify the aspect of the root node
t.img_style['size'] = 30
t.img_style['fgcolor'] = 'blue'

t.show(tree_style=ts)
../_images/node_style_red_and_blue_nodes.png

Static node styles, set through the set_style method, will be attached to the nodes and exported as part of their information. For instance, copy will replicate all node styles in the replicate tree. Note that node styles can also be modified on the fly through layout functions.

Node faces

Node faces are small pieces of graphical information that can be linked to nodes. For instance, text labels or external images could be linked to nodes and they will be plotted within the tree image.

Several types of node faces are provided by the main treeview module, ranging from simple text (TextFace) and geometric shapes (CircleFace), to molecular sequence representations (SequenceFace), heatmaps and profile plots (ProfileFace).

A complete list of available faces can be found at the treeview reference page.

Faces position

Faces can be added to different areas around the node, namely branch-right, branch-top, branch-bottom or aligned. Each area represents a table in which faces can be added through the add_face method. For instance, if you want two text labels drawn below the branch line of a given node, a pair of TextFace faces can be created and added to the columns 0 and 1 of the branch-bottom area:

from ete4 import Tree
from ete4.treeview import TreeStyle, TextFace
t = Tree('((a,b),c);')

# Basic tree style.
ts = TreeStyle()
ts.show_leaf_name = True

# Add two text faces to different columns.
t.add_face(TextFace('hola '), column=0, position='branch-bottom')
t.add_face(TextFace('mundo!'), column=1, position='branch-bottom')
t.show(tree_style=ts)

If you add more than one face to the same area and column, they will be piled up. See the following image as an example of face positions:

possible face positions

(Source code to generate the above image: face_grid_tutorial.py)

Note

Once a face object is created, it can be linked to one or more nodes. For instance, the same text label can be recycled and added to several nodes.

Face properties

Apart from the specific config values of each face type, all face instances contain the same basic attributes that permit to modify general aspects such as margins, background colors, border, etc. A complete list of face attributes can be found in the general Face class documentation. Here is a very simple example:

from ete4 import Tree
from ete4.treeview import TreeStyle, TextFace

t = Tree('(a,b);')

# Basic tree style.
ts = TreeStyle()
ts.show_leaf_name = True

# Create two faces.
hola = TextFace('hola')
mundo = TextFace('mundo')

# Set some attributes.
hola.margin_top = 10
hola.margin_right = 10
hola.margin_left = 10
hola.margin_bottom = 10
hola.opacity = 0.5 # from 0 to 1
hola.inner_border.width = 1 # 1 pixel border
hola.inner_border.type = 1  # dashed line
hola.border.width = 1
hola.background.color = 'LightGreen'

t.add_face(hola, column=0, position='branch-top')
t.add_face(mundo, column=1, position='branch-bottom')

t.show(tree_style=ts)
../_images/face_borders.png

Layout functions

Layout functions act as pre-drawing hooking functions. This means that, before a node is drawn, it is first sent to a layout function. Node properties, style and faces can be then modified on the fly and returned to the drawing engine. Thus, layout functions can be understood as a collection of rules controlling how different nodes should be drawn.

from ete4 import Tree
t = Tree('((((a,b),c),d),e);')

def abc_layout(node):
    vowels = {'a', 'e', 'i', 'o', 'u'}

    if node.name in vowels:
        node.img_style['size'] = 15
        node.img_style['fgcolor'] = 'red'
        # Note that the node style was already initialized with the
        # default values.

# Tree style.
ts = TreeStyle()
ts.show_leaf_name = True
ts.layout_fn = abc_layout

# Add two text faces to different columns.
t.add_face(TextFace('hola '), column=0, position='branch-right')
t.add_face(TextFace('mundo!'), column=1, position='branch-right')

t.show(tree_style=ts)
../_images/example_layout_functions.png

Combining styles, faces and layouts

Examples are probably the best way to show how ETE works:

Fixed node styles

from ete4 import Tree
from ete4.treeview import faces, AttrFace, TreeStyle, NodeStyle


def layout(node):
    # If node is a leaf, add the nodes name and a its scientific name.
    if node.is_leaf:
        faces.add_face_to_node(AttrFace('name'), node, column=0)


def get_example_tree():
    t = Tree()
    t.populate(8)

    # Node style handling is no longer limited to layout functions. You
    # can now create fixed node styles and use them many times, save them
    # or even add them to nodes before drawing (this allows to save and
    # reproduce an tree image design).

    # Set bold red branch to the root node.
    style = NodeStyle()
    style['fgcolor'] = '#0f0f0f'
    style['size'] = 0
    style['vt_line_color'] = '#ff0000'
    style['hz_line_color'] = '#ff0000'
    style['vt_line_width'] = 8
    style['hz_line_width'] = 8
    style['vt_line_type'] = 0  # 0 solid, 1 dashed, 2 dotted
    style['hz_line_type'] = 0
    t.set_style(style)

    # Set dotted red lines to the first two branches.
    style1 = NodeStyle()
    style1['fgcolor'] = '#0f0f0f'
    style1['size'] = 0
    style1['vt_line_color'] = '#ff0000'
    style1['hz_line_color'] = '#ff0000'
    style1['vt_line_width'] = 2
    style1['hz_line_width'] = 2
    style1['vt_line_type'] = 2  # 0 solid, 1 dashed, 2 dotted
    style1['hz_line_type'] = 2
    t.children[0].img_style = style1
    t.children[1].img_style = style1

    # Set dashed blue lines in all leaves.
    style2 = NodeStyle()
    style2['fgcolor'] = '#000000'
    style2['shape'] = 'circle'
    style2['vt_line_color'] = '#0000aa'
    style2['hz_line_color'] = '#0000aa'
    style2['vt_line_width'] = 2
    style2['hz_line_width'] = 2
    style2['vt_line_type'] = 1  # 0 solid, 1 dashed, 2 dotted
    style2['hz_line_type'] = 1
    for l in t.leaves():
        l.img_style = style2

    ts = TreeStyle()
    ts.layout_fn = layout
    ts.show_leaf_name = False

    return t, ts


if __name__ == '__main__':
    t, ts = get_example_tree()
    t.show(tree_style=ts)
    # Or save it with, for example:
    #   t.render('node_style.png', w=400, tree_style=ts)
../_images/node_style.png

Node backgrounds

from ete4 import Tree
from ete4.treeview import faces, AttrFace, TreeStyle, NodeStyle


def layout(node):
    if node.is_leaf:
        N = AttrFace('name', fsize=30)
        faces.add_face_to_node(N, node, 0, position='aligned')


def get_example_tree():
    # Set dashed blue lines in all leaves.
    nst1 = NodeStyle()
    nst1['bgcolor'] = 'LightSteelBlue'
    nst2 = NodeStyle()
    nst2['bgcolor'] = 'Moccasin'
    nst3 = NodeStyle()
    nst3['bgcolor'] = 'DarkSeaGreen'
    nst4 = NodeStyle()
    nst4['bgcolor'] = 'Khaki'

    t = Tree('((((a1,a2),a3), ((b1,b2),(b3,b4))), ((c1,c2),c3));')
    for n in t.traverse():
        n.dist = 0

    n1 = t.common_ancestor(['a1', 'a2', 'a3'])
    n1.set_style(nst1)
    n2 = t.common_ancestor(['b1', 'b2', 'b3', 'b4'])
    n2.set_style(nst2)
    n3 = t.common_ancestor(['c1', 'c2', 'c3'])
    n3.set_style(nst3)
    n4 = t.common_ancestor(['b3', 'b4'])
    n4.set_style(nst4)
    ts = TreeStyle()
    ts.layout_fn = layout
    ts.show_leaf_name = False

    ts.mode = 'c'
    ts.root_opening_factor = 1
    return t, ts


if __name__ == '__main__':
    t, ts = get_example_tree()
    t.show(tree_style=ts)
    # Or save it with:
    #   t.render('node_background.png', w=400, tree_style=ts)
../_images/node_background.png

Img faces

# Import Tree instance and faces module.
from ete4 import Tree
from ete4.treeview import faces, TreeStyle

# Load an example tree.
nw = """
(((Dre:0.008339,Dme:0.300613)1.000000:0.596401,
(Cfa:0.640858,Hsa:0.753230)1.000000:0.182035)1.000000:0.106234,
((Dre:0.271621,Cfa:0.046042)1.000000:0.953250,
(Hsa:0.061813,Mms:0.110769)1.000000:0.204419)1.000000:0.973467);
"""
t = Tree(nw)

# You can create any random tree containing the same leaf names, and
# layout will work equally. For example, creating a random tree with 6
# leaves using a given set of names:
#   t = Tree()
#   t.populate(6, ['Dme', 'Dre', 'Hsa', 'Ptr', 'Cfa', 'Mms'])

# Set the path where images are located.
img_path = './'

# Create faces based on external images.
humanFace = faces.ImgFace(img_path+'human.png')
mouseFace = faces.ImgFace(img_path+'mouse.png')
dogFace = faces.ImgFace(img_path+'dog.png')
chimpFace = faces.ImgFace(img_path+'chimp.png')
fishFace = faces.ImgFace(img_path+'fish.png')
flyFace = faces.ImgFace(img_path+'fly.png')

# Create a face ready to read the name attribute of nodes.
nameFace = faces.AttrFace('name', fsize=20, fgcolor='#009000')

# Create a conversion between leaf names and real names.
code2name = {
    'Dre': 'Drosophila melanogaster',
    'Dme': 'Danio rerio',
    'Hsa': 'Homo sapiens',
    'Ptr': 'Pan troglodytes',
    'Mms': 'Mus musculus',
    'Cfa': 'Canis familiaris'}

# Create a dictionary with the descriptions of each leaf name.
code2desc = {
    'Dre': """The zebrafish, also known as Danio rerio,
is a tropical freshwater fish belonging to the
minnow family (Cyprinidae).""",
    'Dme': """True flies are insects of the order Diptera,
possessing a single pair of wings on the
mesothorax and a pair of halteres, derived from
the hind wings, on the metathorax""",
    'Hsa': """A human is a member of a species
of bipedal primates in the family Hominidae.""",
    'Ptr': """Chimpanzee, sometimes colloquially
chimp, is the common name for the
two extant species of ape in the genus Pan.""",
    'Mms': """A mouse is a small mammal belonging to the
order of rodents.""",
    'Cfa': """The dog (Canis lupus familiaris) is a
domesticated subspecies of the Gray Wolf,
a member of the Canidae family of the
orderCarnivora."""}

# Create a layout function. We will use all previously created faces
# and will set different node styles depending on the type of node.
def mylayout(node):
    # If node is a leaf, add the node names and their scientific names.
    if node.is_leaf:
        # Add an static face that handles the node name.
        faces.add_face_to_node(nameFace, node, column=0)
        # We can also create faces on the fly.
        longNameFace = faces.TextFace(code2name[node.name])
        faces.add_face_to_node(longNameFace, node, column=0)

        # Text faces support multiline. We add a text face with the
        # whole description of each leaf.
        descFace = faces.TextFace(code2desc[node.name], fsize=10)
        descFace.margin_top = 10
        descFace.margin_bottom = 10
        descFace.border.margin = 1

        # Note that this faces are added in "aligned" mode.
        faces.add_face_to_node(descFace, node, column=0, aligned=True)

        # Set the style of leaf nodes.
        node.img_style['size'] = 12
        node.img_style['shape'] = 'circle'
    else:  # for internal nodes
        # Set the style of internal nodes.
        node.img_style['size'] = 6
        node.img_style['shape'] = 'circle'
        node.img_style['fgcolor'] = '#000000'

    # If an internal node contains more than 4 leaves, add the images
    # of the represented species sorted in columns of 2 images max.
    if len(node)>=4:
        col = 0
        for i, name in enumerate(set(node.leaf_names())):
            if i > 0 and i % 2 == 0:
                col += 1
            # Add the corresponding face to the node
            if name.startswith('Dme'):
                faces.add_face_to_node(flyFace, node, column=col)
            elif name.startswith('Dre'):
                faces.add_face_to_node(fishFace, node, column=col)
            elif name.startswith('Mms'):
                faces.add_face_to_node(mouseFace, node, column=col)
            elif name.startswith('Ptr'):
                faces.add_face_to_node(chimpFace, node, column=col)
            elif name.startswith('Hsa'):
                faces.add_face_to_node(humanFace, node, column=col)
            elif name.startswith('Cfa'):
                faces.add_face_to_node(dogFace, node, column=col)

            # Modifies this node's style
            node.img_style['size'] = 16
            node.img_style['shape'] = 'sphere'
            node.img_style['fgcolor'] = '#AA0000'

    # If leaf is 'Hsa' (homo sapiens), highlight it using a different
    # background.
    if node.is_leaf and node.name.startswith('Hsa'):
        node.img_style['bgcolor'] = '#9db0cf'

# And, finally, visualize the tree using our own layout function.
ts = TreeStyle()
ts.layout_fn = mylayout

t.render('img_faces.png', w=600, tree_style=ts)
../_images/img_faces.png

Note that images are attached to terminal and internal nodes.

Bubble tree maps

import random

from ete4 import Tree
from ete4.treeview import TreeStyle, NodeStyle, faces, AttrFace, CircleFace


def layout(node):
    if node.is_leaf:
        # Add node name to leaf nodes.
        N = AttrFace('name', fsize=14, fgcolor='black')
        faces.add_face_to_node(N, node, 0)

    if 'weight' in node.props:
        # Create a sphere face whose size is proportional to the node's  'weight'.
        C = CircleFace(radius=node.props['weight'], color='RoyalBlue', style='sphere')
        # Let's make the sphere transparent.
        C.opacity = 0.3
        # And place it as a float face over the tree.
        faces.add_face_to_node(C, node, 0, position='float')


def get_example_tree():
    # Make a random tree.
    t = Tree()
    t.populate(20, random_branches=True)

    # Add some random weight to all nodes.
    for n in t.traverse():
        n.add_props(weight=random.randint(0, 50))

    # Create an empty TreeStyle.
    ts = TreeStyle()

    # Set our custom layout function.
    ts.layout_fn = layout

    # Draw a circular tree.
    ts.mode = 'c'

    # We will add node names manually.
    ts.show_leaf_name = False
    # Show branch data.
    ts.show_branch_length = True
    ts.show_branch_support = True

    return t, ts


if __name__ == '__main__':
    t, ts = get_example_tree()
    t.show(tree_style=ts)
    # If we want to save it, we can do:
    #   t.render('bubble_map.png', w=600, dpi=300, tree_style=ts)
../_images/bubble_map.png

Trees within trees

import random

from ete4 import Tree
from ete4.treeview import TreeStyle, NodeStyle, faces, AttrFace, TreeFace


# Tree Style used to render small trees used as leaf faces.
small_ts = TreeStyle()
small_ts.show_leaf_name = True
small_ts.scale = 10


def layout(node):
    if not node.is_leaf:
        return

    # Add node name to laef nodes
    N = AttrFace('name', fsize=14, fgcolor='black')
    faces.add_face_to_node(N, node, 0)

    t = Tree()
    t.populate(10)

    T = TreeFace(t, small_ts)
    # Let's make the sphere transparent
    T.opacity = 0.8
    # And place as a float face over the tree
    faces.add_face_to_node(T, node, 1, position='aligned')


def get_example_tree():
    # Random tree.
    t = Tree()
    t.populate(20, random_branches=True)

    # Some random features in all nodes
    for n in t.traverse():
        n.add_props(weight=random.randint(0, 50))

    # Create an empty TreeStyle
    ts = TreeStyle()

    # Set our custom layout function
    ts.layout_fn = layout

    # Draw a tree
    ts.mode = 'c'

    # We will add node names manually.
    ts.show_leaf_name = False
    # Show branch data.
    ts.show_branch_length = True
    ts.show_branch_support = True

    return t, ts


if __name__ == '__main__':
    t, ts = get_example_tree()
    t.show(tree_style=ts)
    # Or save with:
    #   t.render('tree_faces.png', w=600, dpi=300, tree_style=ts)
../_images/tree_faces.png

Phylogenetic trees and sequence domains

from ete4 import Tree
from ete4.treeview import SeqMotifFace, TreeStyle, add_face_to_node


seq = ('-----------------------------------------------AQAK---IKGSKKAIKVFSSA---'
       'APERLQEYGSIFTDA---GLQRRPRHRIQSK-------ALQEKLKDFPVCVSTKPEPEDDAEEGLGGLPSN'
       'ISSVSSLLLFNTTENLYKKYVFLDPLAG----THVMLGAETEEKLFDAPLSISKREQLEQQVPENYFYVPD'
       'LGQVPEIDVPSYLPDLPGIANDLMYIADLGPGIAPSAPGTIPELPTFHTEVAEPLKVGELGSGMGAGPGTP'
       'AHTPSSLDTPHFVFQTYKMGAPPLPPSTAAPVGQGARQDDSSSSASPSVQGAPREVVDPSGGWATLLESIR'
       'QAGGIGKAKLRSMKERKLEKQQQKEQEQVRATSQGGHL--MSDLFNKLVMRRKGISGKGPGAGDGPGGAFA'
       'RVSDSIPPLPPPQQPQAEDED----')

mixed_motifs = [
        # seq.start, seq.end, shape, width, height, fgcolor, bgcolor
        [10, 100, '[]', None, 10, 'black', 'rgradient:blue', 'arial|8|white|long text clipped long text clipped'],
        [101, 150, 'o', None, 10, 'blue', 'pink', None],
        [155, 180, '()', None, 10, 'blue', 'rgradient:purple', None],
        [160, 190, '^', None, 14, 'black', 'yellow', None],
        [191, 200, '<>', None, 12, 'black', 'rgradient:orange', None],
        [201, 250, 'o', None, 12, 'black', 'brown', None],
        [351, 370, 'v', None, 15, 'black', 'rgradient:gold', None],
        [370, 420, 'compactseq', 2, 10, None, None, None],
]

simple_motifs = [
        # seq.start, seq.end, shape, width, height, fgcolor, bgcolor
        [10, 60, '[]', None, 10, 'black', 'rgradient:blue', 'arial|8|white|long text clipped long text clipped'],
        [120, 150, 'o', None, 10, 'blue', 'pink', None],
        [200, 300, '()', None, 10, 'blue', 'red', 'arial|8|white|hello'],
]

box_motifs = [
        # seq.start, seq.end, shape, width, height, fgcolor, bgcolor
        [0,  5, '[]', None, 10, 'black', 'rgradient:blue', 'arial|8|white|10'],
        [10, 25, '[]', None, 10, 'black', 'rgradient:ref', 'arial|8|white|10'],
        [30, 45, '[]', None, 10, 'black', 'rgradient:orange', 'arial|8|white|20'],
        [50, 65, '[]', None, 10, 'black', 'rgradient:pink', 'arial|8|white|20'],
        [70, 85, '[]', None, 10, 'black', 'rgradient:green', 'arial|8|white|20'],
        [90, 105, '[]', None, 10, 'black', 'rgradient:brown', 'arial|8|white|20'],
        [110, 125, '[]', None, 10, 'black', 'rgradient:yellow', 'arial|8|white|20'],
]


def get_example_tree():
        # Create a random tree and add to each leaf a random set of motifs
        # from the original set
        t = Tree('( (A, B, C, D, E, F, G), H, I);')

        seqFace = SeqMotifFace(seq, gapcolor='red')
        t['A'].add_face(seqFace, 0, 'aligned')

        seqFace = SeqMotifFace(seq, seq_format='line', gap_format='blank')
        t['B'].add_face(seqFace, 0, 'aligned')

        seqFace = SeqMotifFace(seq, seq_format='line')
        t['C'].add_face(seqFace, 0, 'aligned')

        seqFace = SeqMotifFace(seq, seq_format='()')
        t['D'].add_face(seqFace, 0, 'aligned')

        seqFace = SeqMotifFace(seq, motifs=simple_motifs, seq_format='-')
        t['E'].add_face(seqFace, 0, 'aligned')

        seqFace = SeqMotifFace(seq=None, motifs=simple_motifs, gap_format='blank')
        t['F'].add_face(seqFace, 0, 'aligned')

        seqFace = SeqMotifFace(seq, motifs=mixed_motifs, seq_format='-')
        t['G'].add_face(seqFace, 0, 'aligned')

        seqFace = SeqMotifFace(seq=None, motifs=box_motifs, gap_format='line')
        t['H'].add_face(seqFace, 0, 'aligned')

        seqFace = SeqMotifFace(seq[30:60], seq_format='seq')
        t['I'].add_face(seqFace, 0, 'aligned')

        return t


if __name__ == '__main__':
    t = get_example_tree()
    ts = TreeStyle()
    ts.tree_width = 50
    t.render('seq_motif_faces.png', tree_style=ts)
../_images/seq_motif_faces.png

Creating your custom interactive faces

# To play with random colors.
import colorsys
import random

# We will need to create Qt items.
from PyQt6 import QtCore
from PyQt6.QtGui import QColor, QPen, QBrush
from PyQt6.QtWidgets import QGraphicsRectItem, QGraphicsSimpleTextItem, QGraphicsEllipseItem

from ete4 import Tree
from ete4.treeview import faces, TreeStyle, NodeStyle


class InteractiveItem(QGraphicsRectItem):
    def __init__(self, *arg, **karg):
        super().__init__(*arg, **karg)
        self.node = None
        self.label = None
        self.setCursor(QtCore.Qt.PointingHandCursor)
        self.setAcceptsHoverEvents(True)

    def hoverEnterEvent(self, e):
        # There are many ways of adding interactive elements. With the
        # following code, I show/hide a text item over my custom
        # DynamicItemFace
        if not self.label:
            self.label = QGraphicsRectItem()
            self.label.setParentItem(self)
            # This is to ensure that the label is rendered over the
            # rest of item children (default ZValue for items is 0)
            self.label.setZValue(1)
            self.label.setBrush(QBrush(QColor('white')))
            self.label.text = QGraphicsSimpleTextItem()
            self.label.text.setParentItem(self.label)

        self.label.text.setText(self.node.name)
        self.label.setRect(self.label.text.boundingRect())
        self.label.setVisible(True)

    def hoverLeaveEvent(self, e):
        if self.label:
            self.label.setVisible(False)


def random_color(hue=None):
    """Return a random color in RGB format."""
    hue = hue if hue is not None else random.random()

    luminosity = 0.5
    saturation = 0.5

    rgb = colorsys.hls_to_rgb(hue, luminosity, saturation)

    return '#%02x%02x%02x' % tuple([int(x * 255) for x in rgb])


def ugly_name_face(node, *args, **kargs):
    """Item generator. It must receive a node object, and return a Qt
    graphics item that can be used as a node face.
    """

    # receive an arbitrary number of arguments, in this case width and
    # height of the faces
    width = args[0][0]
    height = args[0][1]

    ## Creates a main master Item that will contain all other elements
    ## Items can be standard QGraphicsItem
    # masterItem = QGraphicsRectItem(0, 0, width, height)

    # Or your custom Items, in which you can re-implement interactive
    # functions, etc. Check QGraphicsItem doc for details.
    masterItem = InteractiveItem(0, 0, width, height)

    # Keep a link within the item to access node info.
    masterItem.node = node

    # No border around the masterItem.
    masterItem.setPen(QPen(QtCore.Qt.NoPen))

    # Add ellipse around text.
    ellipse = QGraphicsEllipseItem(masterItem.rect())
    ellipse.setParentItem(masterItem)
    # Change ellipse color.
    ellipse.setBrush(QBrush(QColor( random_color())))

    # Add node name within the ellipse.
    text = QGraphicsSimpleTextItem(node.name)
    text.setParentItem(ellipse)
    text.setPen(QPen(QPen(QColor('white'))))

    # Center text according to masterItem size.
    tw = text.boundingRect().width()
    th = text.boundingRect().height()
    center = masterItem.boundingRect().center()
    text.setPos(center.x()-tw/2, center.y()-th/2)

    return masterItem


def master_ly(node):
    if node.is_leaf:
        # Create an ItemFAce. First argument must be the pointer to
        # the constructor function that returns a QGraphicsItem. It
        # will be used to draw the Face. Next arguments are arbitrary,
        # and they will be forwarded to the constructor Face function.
        F = faces.DynamicItemFace(ugly_name_face, 100, 50)
        faces.add_face_to_node(F, node, 0, position='aligned')


def get_example_tree():
    t = Tree()
    t.populate(8)

    ts = TreeStyle()
    ts.layout_fn = master_ly
    ts.title.add_face(faces.TextFace('Drawing your own Qt Faces', fsize=15), 0)

    return t, ts


if __name__ == '__main__':
    t, ts = get_example_tree()

    # The interactive features are only available using the GUI.
    t.show(tree_style=ts)
../_images/item_faces.png

Note that the faces shown in this image are not static. When the tree is viewed using the tree.show() method, you can interact with items.