784 lines
19 KiB
Markdown
784 lines
19 KiB
Markdown
# ETE Toolkit Visualization Guide
|
|
|
|
Complete guide to tree visualization with ETE Toolkit.
|
|
|
|
## Table of Contents
|
|
1. [Rendering Basics](#rendering-basics)
|
|
2. [TreeStyle Configuration](#treestyle-configuration)
|
|
3. [Node Styling](#node-styling)
|
|
4. [Faces](#faces)
|
|
5. [Layout Functions](#layout-functions)
|
|
6. [Advanced Visualization](#advanced-visualization)
|
|
|
|
---
|
|
|
|
## Rendering Basics
|
|
|
|
### Output Formats
|
|
|
|
ETE supports three main output formats:
|
|
|
|
```python
|
|
from ete3 import Tree
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# PNG (raster, good for presentations)
|
|
tree.render("output.png", w=800, h=600, units="px", dpi=300)
|
|
|
|
# PDF (vector, good for publications)
|
|
tree.render("output.pdf", w=200, units="mm")
|
|
|
|
# SVG (vector, editable)
|
|
tree.render("output.svg")
|
|
```
|
|
|
|
### Units and Dimensions
|
|
|
|
```python
|
|
# Pixels
|
|
tree.render("tree.png", w=1200, h=800, units="px")
|
|
|
|
# Millimeters
|
|
tree.render("tree.pdf", w=210, h=297, units="mm") # A4 size
|
|
|
|
# Inches
|
|
tree.render("tree.pdf", w=8.5, h=11, units="in") # US Letter
|
|
|
|
# Auto-size (aspect ratio preserved)
|
|
tree.render("tree.pdf", w=200, units="mm") # Height auto-calculated
|
|
```
|
|
|
|
### Interactive Visualization
|
|
|
|
```python
|
|
from ete3 import Tree
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Launch GUI
|
|
# - Zoom with mouse wheel
|
|
# - Pan by dragging
|
|
# - Search with Ctrl+F
|
|
# - Export from menu
|
|
# - Edit node properties
|
|
tree.show()
|
|
```
|
|
|
|
---
|
|
|
|
## TreeStyle Configuration
|
|
|
|
### Basic TreeStyle Options
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle
|
|
|
|
tree = Tree("tree.nw")
|
|
ts = TreeStyle()
|
|
|
|
# Display options
|
|
ts.show_leaf_name = True # Show leaf names
|
|
ts.show_branch_length = True # Show branch lengths
|
|
ts.show_branch_support = True # Show support values
|
|
ts.show_scale = True # Show scale bar
|
|
|
|
# Branch length scaling
|
|
ts.scale = 50 # Pixels per branch length unit
|
|
ts.min_leaf_separation = 10 # Minimum space between leaves (pixels)
|
|
|
|
# Layout orientation
|
|
ts.rotation = 0 # 0=left-to-right, 90=top-to-bottom
|
|
ts.branch_vertical_margin = 10 # Vertical spacing between branches
|
|
|
|
# Tree shape
|
|
ts.mode = "r" # "r"=rectangular (default), "c"=circular
|
|
|
|
tree.render("tree.pdf", tree_style=ts)
|
|
```
|
|
|
|
### Circular Trees
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle
|
|
|
|
tree = Tree("tree.nw")
|
|
ts = TreeStyle()
|
|
|
|
# Circular mode
|
|
ts.mode = "c"
|
|
ts.arc_start = 0 # Starting angle (degrees)
|
|
ts.arc_span = 360 # Angular span (degrees, 360=full circle)
|
|
|
|
# For semicircle
|
|
ts.arc_start = -180
|
|
ts.arc_span = 180
|
|
|
|
tree.render("circular_tree.pdf", tree_style=ts)
|
|
```
|
|
|
|
### Title and Legend
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, TextFace
|
|
|
|
tree = Tree("tree.nw")
|
|
ts = TreeStyle()
|
|
|
|
# Add title
|
|
title = TextFace("Phylogenetic Tree of Species", fsize=20, bold=True)
|
|
ts.title.add_face(title, column=0)
|
|
|
|
# Add legend
|
|
ts.legend.add_face(TextFace("Red nodes: High support", fsize=10), column=0)
|
|
ts.legend.add_face(TextFace("Blue nodes: Low support", fsize=10), column=0)
|
|
|
|
# Legend position
|
|
ts.legend_position = 1 # 1=top-right, 2=top-left, 3=bottom-left, 4=bottom-right
|
|
|
|
tree.render("tree_with_legend.pdf", tree_style=ts)
|
|
```
|
|
|
|
### Custom Background
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle
|
|
|
|
tree = Tree("tree.nw")
|
|
ts = TreeStyle()
|
|
|
|
# Background color
|
|
ts.bgcolor = "#f0f0f0" # Light gray background
|
|
|
|
# Tree border
|
|
ts.show_border = True
|
|
|
|
tree.render("tree_background.pdf", tree_style=ts)
|
|
```
|
|
|
|
---
|
|
|
|
## Node Styling
|
|
|
|
### NodeStyle Properties
|
|
|
|
```python
|
|
from ete3 import Tree, NodeStyle
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
for node in tree.traverse():
|
|
nstyle = NodeStyle()
|
|
|
|
# Node size and shape
|
|
nstyle["size"] = 10 # Node size in pixels
|
|
nstyle["shape"] = "circle" # "circle", "square", "sphere"
|
|
|
|
# Colors
|
|
nstyle["fgcolor"] = "blue" # Foreground color (node itself)
|
|
nstyle["bgcolor"] = "lightblue" # Background color (only for sphere)
|
|
|
|
# Line style for branches
|
|
nstyle["hz_line_type"] = 0 # 0=solid, 1=dashed, 2=dotted
|
|
nstyle["vt_line_type"] = 0 # Vertical line type
|
|
nstyle["hz_line_color"] = "black" # Horizontal line color
|
|
nstyle["vt_line_color"] = "black" # Vertical line color
|
|
nstyle["hz_line_width"] = 2 # Line width in pixels
|
|
nstyle["vt_line_width"] = 2
|
|
|
|
node.set_style(nstyle)
|
|
|
|
tree.render("styled_tree.pdf")
|
|
```
|
|
|
|
### Conditional Styling
|
|
|
|
```python
|
|
from ete3 import Tree, NodeStyle
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Style based on node properties
|
|
for node in tree.traverse():
|
|
nstyle = NodeStyle()
|
|
|
|
if node.is_leaf():
|
|
# Leaf node style
|
|
nstyle["size"] = 8
|
|
nstyle["fgcolor"] = "darkgreen"
|
|
nstyle["shape"] = "circle"
|
|
else:
|
|
# Internal node style based on support
|
|
if node.support > 0.9:
|
|
nstyle["size"] = 6
|
|
nstyle["fgcolor"] = "red"
|
|
nstyle["shape"] = "sphere"
|
|
else:
|
|
nstyle["size"] = 4
|
|
nstyle["fgcolor"] = "gray"
|
|
nstyle["shape"] = "circle"
|
|
|
|
# Style branches by length
|
|
if node.dist > 1.0:
|
|
nstyle["hz_line_width"] = 3
|
|
nstyle["hz_line_color"] = "blue"
|
|
else:
|
|
nstyle["hz_line_width"] = 1
|
|
nstyle["hz_line_color"] = "black"
|
|
|
|
node.set_style(nstyle)
|
|
|
|
tree.render("conditional_styled_tree.pdf")
|
|
```
|
|
|
|
### Hiding Nodes
|
|
|
|
```python
|
|
from ete3 import Tree, NodeStyle
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Hide specific nodes
|
|
for node in tree.traverse():
|
|
if node.support < 0.5: # Hide low support nodes
|
|
nstyle = NodeStyle()
|
|
nstyle["draw_descendants"] = False # Don't draw this node's subtree
|
|
nstyle["size"] = 0 # Make node invisible
|
|
node.set_style(nstyle)
|
|
|
|
tree.render("filtered_tree.pdf")
|
|
```
|
|
|
|
---
|
|
|
|
## Faces
|
|
|
|
Faces are graphical elements attached to nodes. They appear at specific positions around nodes.
|
|
|
|
### Face Positions
|
|
|
|
- `"branch-right"`: Right side of branch (after node)
|
|
- `"branch-top"`: Above branch
|
|
- `"branch-bottom"`: Below branch
|
|
- `"aligned"`: Aligned column at tree edge (for leaves)
|
|
|
|
### TextFace
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, TextFace
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
def layout(node):
|
|
if node.is_leaf():
|
|
# Add species name
|
|
name_face = TextFace(node.name, fsize=12, fgcolor="black")
|
|
node.add_face(name_face, column=0, position="branch-right")
|
|
|
|
# Add additional text
|
|
info_face = TextFace(f"Length: {node.dist:.3f}", fsize=8, fgcolor="gray")
|
|
node.add_face(info_face, column=1, position="branch-right")
|
|
else:
|
|
# Add support value
|
|
if node.support:
|
|
support_face = TextFace(f"{node.support:.2f}", fsize=8, fgcolor="red")
|
|
node.add_face(support_face, column=0, position="branch-top")
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
ts.show_leaf_name = False # We're adding custom names
|
|
|
|
tree.render("tree_textfaces.pdf", tree_style=ts)
|
|
```
|
|
|
|
### AttrFace
|
|
|
|
Display node attributes directly:
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, AttrFace
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Add custom attributes
|
|
for leaf in tree:
|
|
leaf.add_feature("habitat", "aquatic" if "fish" in leaf.name else "terrestrial")
|
|
leaf.add_feature("temperature", 20)
|
|
|
|
def layout(node):
|
|
if node.is_leaf():
|
|
# Display attribute directly
|
|
habitat_face = AttrFace("habitat", fsize=10)
|
|
node.add_face(habitat_face, column=0, position="aligned")
|
|
|
|
temp_face = AttrFace("temperature", fsize=10)
|
|
node.add_face(temp_face, column=1, position="aligned")
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
|
|
tree.render("tree_attrfaces.pdf", tree_style=ts)
|
|
```
|
|
|
|
### CircleFace
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, CircleFace, TextFace
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Annotate with habitat
|
|
for leaf in tree:
|
|
leaf.add_feature("habitat", "marine" if "fish" in leaf.name else "land")
|
|
|
|
def layout(node):
|
|
if node.is_leaf():
|
|
# Colored circle based on habitat
|
|
color = "blue" if node.habitat == "marine" else "green"
|
|
circle = CircleFace(radius=5, color=color, style="circle")
|
|
node.add_face(circle, column=0, position="aligned")
|
|
|
|
# Label
|
|
name = TextFace(node.name, fsize=10)
|
|
node.add_face(name, column=1, position="aligned")
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
ts.show_leaf_name = False
|
|
|
|
tree.render("tree_circles.pdf", tree_style=ts)
|
|
```
|
|
|
|
### ImgFace
|
|
|
|
Add images to nodes:
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, ImgFace, TextFace
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
def layout(node):
|
|
if node.is_leaf():
|
|
# Add species image
|
|
img_path = f"images/{node.name}.png" # Path to image
|
|
try:
|
|
img_face = ImgFace(img_path, width=50, height=50)
|
|
node.add_face(img_face, column=0, position="aligned")
|
|
except:
|
|
pass # Skip if image doesn't exist
|
|
|
|
# Add name
|
|
name_face = TextFace(node.name, fsize=10)
|
|
node.add_face(name_face, column=1, position="aligned")
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
ts.show_leaf_name = False
|
|
|
|
tree.render("tree_images.pdf", tree_style=ts)
|
|
```
|
|
|
|
### BarChartFace
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, BarChartFace, TextFace
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Add data for bar charts
|
|
for leaf in tree:
|
|
leaf.add_feature("values", [1.2, 2.3, 0.5, 1.8]) # Multiple values
|
|
|
|
def layout(node):
|
|
if node.is_leaf():
|
|
# Add bar chart
|
|
chart = BarChartFace(
|
|
node.values,
|
|
width=100,
|
|
height=40,
|
|
colors=["red", "blue", "green", "orange"],
|
|
labels=["A", "B", "C", "D"]
|
|
)
|
|
node.add_face(chart, column=0, position="aligned")
|
|
|
|
# Add name
|
|
name = TextFace(node.name, fsize=10)
|
|
node.add_face(name, column=1, position="aligned")
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
ts.show_leaf_name = False
|
|
|
|
tree.render("tree_barcharts.pdf", tree_style=ts)
|
|
```
|
|
|
|
### PieChartFace
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, PieChartFace, TextFace
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Add data
|
|
for leaf in tree:
|
|
leaf.add_feature("proportions", [25, 35, 40]) # Percentages
|
|
|
|
def layout(node):
|
|
if node.is_leaf():
|
|
# Add pie chart
|
|
pie = PieChartFace(
|
|
node.proportions,
|
|
width=30,
|
|
height=30,
|
|
colors=["red", "blue", "green"]
|
|
)
|
|
node.add_face(pie, column=0, position="aligned")
|
|
|
|
name = TextFace(node.name, fsize=10)
|
|
node.add_face(name, column=1, position="aligned")
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
ts.show_leaf_name = False
|
|
|
|
tree.render("tree_piecharts.pdf", tree_style=ts)
|
|
```
|
|
|
|
### SequenceFace (for alignments)
|
|
|
|
```python
|
|
from ete3 import PhyloTree, TreeStyle, SeqMotifFace
|
|
|
|
tree = PhyloTree("tree.nw")
|
|
tree.link_to_alignment("alignment.fasta")
|
|
|
|
def layout(node):
|
|
if node.is_leaf():
|
|
# Display sequence
|
|
seq_face = SeqMotifFace(node.sequence, seq_format="seq")
|
|
node.add_face(seq_face, column=0, position="aligned")
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
ts.show_leaf_name = True
|
|
|
|
tree.render("tree_alignment.pdf", tree_style=ts)
|
|
```
|
|
|
|
---
|
|
|
|
## Layout Functions
|
|
|
|
Layout functions are Python functions that modify node appearance during rendering.
|
|
|
|
### Basic Layout Function
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, TextFace
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
def my_layout(node):
|
|
"""Called for every node before rendering"""
|
|
|
|
if node.is_leaf():
|
|
# Add text to leaves
|
|
name_face = TextFace(node.name.upper(), fsize=12, fgcolor="blue")
|
|
node.add_face(name_face, column=0, position="branch-right")
|
|
else:
|
|
# Add support to internal nodes
|
|
if node.support:
|
|
support_face = TextFace(f"BS: {node.support:.0f}", fsize=8)
|
|
node.add_face(support_face, column=0, position="branch-top")
|
|
|
|
# Apply layout function
|
|
ts = TreeStyle()
|
|
ts.layout_fn = my_layout
|
|
ts.show_leaf_name = False
|
|
|
|
tree.render("tree_custom_layout.pdf", tree_style=ts)
|
|
```
|
|
|
|
### Dynamic Styling in Layout
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, NodeStyle, TextFace
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
def layout(node):
|
|
# Modify node style dynamically
|
|
nstyle = NodeStyle()
|
|
|
|
# Color by clade
|
|
if "clade_A" in [l.name for l in node.get_leaves()]:
|
|
nstyle["bgcolor"] = "lightblue"
|
|
elif "clade_B" in [l.name for l in node.get_leaves()]:
|
|
nstyle["bgcolor"] = "lightgreen"
|
|
|
|
node.set_style(nstyle)
|
|
|
|
# Add faces based on features
|
|
if hasattr(node, "annotation"):
|
|
text = TextFace(node.annotation, fsize=8)
|
|
node.add_face(text, column=0, position="branch-top")
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
|
|
tree.render("tree_dynamic.pdf", tree_style=ts)
|
|
```
|
|
|
|
### Multiple Column Layout
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, TextFace, CircleFace
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Add features
|
|
for leaf in tree:
|
|
leaf.add_feature("habitat", "aquatic")
|
|
leaf.add_feature("temp", 20)
|
|
leaf.add_feature("depth", 100)
|
|
|
|
def layout(node):
|
|
if node.is_leaf():
|
|
# Column 0: Name
|
|
name = TextFace(node.name, fsize=10)
|
|
node.add_face(name, column=0, position="aligned")
|
|
|
|
# Column 1: Habitat indicator
|
|
color = "blue" if node.habitat == "aquatic" else "brown"
|
|
circle = CircleFace(radius=5, color=color)
|
|
node.add_face(circle, column=1, position="aligned")
|
|
|
|
# Column 2: Temperature
|
|
temp = TextFace(f"{node.temp}°C", fsize=8)
|
|
node.add_face(temp, column=2, position="aligned")
|
|
|
|
# Column 3: Depth
|
|
depth = TextFace(f"{node.depth}m", fsize=8)
|
|
node.add_face(depth, column=3, position="aligned")
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
ts.show_leaf_name = False
|
|
|
|
tree.render("tree_columns.pdf", tree_style=ts)
|
|
```
|
|
|
|
---
|
|
|
|
## Advanced Visualization
|
|
|
|
### Highlighting Clades
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, NodeStyle, TextFace
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Define clades to highlight
|
|
clade_members = {
|
|
"Clade_A": ["species1", "species2", "species3"],
|
|
"Clade_B": ["species4", "species5"]
|
|
}
|
|
|
|
def layout(node):
|
|
# Check if node is ancestor of specific clade
|
|
node_leaves = set([l.name for l in node.get_leaves()])
|
|
|
|
for clade_name, members in clade_members.items():
|
|
if set(members).issubset(node_leaves):
|
|
# This node is ancestor of the clade
|
|
nstyle = NodeStyle()
|
|
nstyle["bgcolor"] = "yellow"
|
|
nstyle["size"] = 0
|
|
|
|
# Add label
|
|
if set(members) == node_leaves: # Exact match
|
|
label = TextFace(clade_name, fsize=14, bold=True, fgcolor="red")
|
|
node.add_face(label, column=0, position="branch-top")
|
|
|
|
node.set_style(nstyle)
|
|
break
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
|
|
tree.render("tree_highlighted_clades.pdf", tree_style=ts)
|
|
```
|
|
|
|
### Collapsing Clades
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, TextFace, NodeStyle
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Define which clades to collapse
|
|
clades_to_collapse = ["clade1_species1", "clade1_species2"]
|
|
|
|
def layout(node):
|
|
if not node.is_leaf():
|
|
node_leaves = [l.name for l in node.get_leaves()]
|
|
|
|
# Check if this is a clade we want to collapse
|
|
if all(l in clades_to_collapse for l in node_leaves):
|
|
# Collapse by hiding descendants
|
|
nstyle = NodeStyle()
|
|
nstyle["draw_descendants"] = False
|
|
nstyle["size"] = 20
|
|
nstyle["fgcolor"] = "steelblue"
|
|
nstyle["shape"] = "sphere"
|
|
node.set_style(nstyle)
|
|
|
|
# Add label showing what's collapsed
|
|
label = TextFace(f"[{len(node_leaves)} species]", fsize=10)
|
|
node.add_face(label, column=0, position="branch-right")
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
|
|
tree.render("tree_collapsed.pdf", tree_style=ts)
|
|
```
|
|
|
|
### Heat Map Visualization
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, RectFace, TextFace
|
|
import numpy as np
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Generate random data for heatmap
|
|
for leaf in tree:
|
|
leaf.add_feature("data", np.random.rand(10)) # 10 data points
|
|
|
|
def layout(node):
|
|
if node.is_leaf():
|
|
# Add name
|
|
name = TextFace(node.name, fsize=8)
|
|
node.add_face(name, column=0, position="aligned")
|
|
|
|
# Add heatmap cells
|
|
for i, value in enumerate(node.data):
|
|
# Color based on value
|
|
intensity = int(255 * value)
|
|
color = f"#{255-intensity:02x}{intensity:02x}00" # Green-red gradient
|
|
|
|
rect = RectFace(width=20, height=15, fgcolor=color, bgcolor=color)
|
|
node.add_face(rect, column=i+1, position="aligned")
|
|
|
|
# Add column headers
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
ts.show_leaf_name = False
|
|
|
|
# Add header
|
|
for i in range(10):
|
|
header = TextFace(f"C{i+1}", fsize=8, fgcolor="gray")
|
|
ts.aligned_header.add_face(header, column=i+1)
|
|
|
|
tree.render("tree_heatmap.pdf", tree_style=ts)
|
|
```
|
|
|
|
### Phylogenetic Events Visualization
|
|
|
|
```python
|
|
from ete3 import PhyloTree, TreeStyle, TextFace, NodeStyle
|
|
|
|
tree = PhyloTree("gene_tree.nw")
|
|
tree.set_species_naming_function(lambda x: x.split("_")[0])
|
|
tree.get_descendant_evol_events()
|
|
|
|
def layout(node):
|
|
# Style based on evolutionary event
|
|
if hasattr(node, "evoltype"):
|
|
nstyle = NodeStyle()
|
|
|
|
if node.evoltype == "D": # Duplication
|
|
nstyle["fgcolor"] = "red"
|
|
nstyle["size"] = 10
|
|
nstyle["shape"] = "square"
|
|
|
|
label = TextFace("DUP", fsize=8, fgcolor="red", bold=True)
|
|
node.add_face(label, column=0, position="branch-top")
|
|
|
|
elif node.evoltype == "S": # Speciation
|
|
nstyle["fgcolor"] = "blue"
|
|
nstyle["size"] = 6
|
|
nstyle["shape"] = "circle"
|
|
|
|
node.set_style(nstyle)
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
ts.show_leaf_name = True
|
|
|
|
tree.render("gene_tree_events.pdf", tree_style=ts)
|
|
```
|
|
|
|
### Custom Tree with Legend
|
|
|
|
```python
|
|
from ete3 import Tree, TreeStyle, TextFace, CircleFace, NodeStyle
|
|
|
|
tree = Tree("tree.nw")
|
|
|
|
# Categorize species
|
|
for leaf in tree:
|
|
if "fish" in leaf.name.lower():
|
|
leaf.add_feature("category", "fish")
|
|
elif "bird" in leaf.name.lower():
|
|
leaf.add_feature("category", "bird")
|
|
else:
|
|
leaf.add_feature("category", "mammal")
|
|
|
|
category_colors = {
|
|
"fish": "blue",
|
|
"bird": "green",
|
|
"mammal": "red"
|
|
}
|
|
|
|
def layout(node):
|
|
if node.is_leaf():
|
|
# Color by category
|
|
nstyle = NodeStyle()
|
|
nstyle["fgcolor"] = category_colors[node.category]
|
|
nstyle["size"] = 10
|
|
node.set_style(nstyle)
|
|
|
|
ts = TreeStyle()
|
|
ts.layout_fn = layout
|
|
|
|
# Add legend
|
|
ts.legend.add_face(TextFace("Legend:", fsize=12, bold=True), column=0)
|
|
for category, color in category_colors.items():
|
|
circle = CircleFace(radius=5, color=color)
|
|
ts.legend.add_face(circle, column=0)
|
|
label = TextFace(f" {category.capitalize()}", fsize=10)
|
|
ts.legend.add_face(label, column=1)
|
|
|
|
ts.legend_position = 1
|
|
|
|
tree.render("tree_with_legend.pdf", tree_style=ts)
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
1. **Use layout functions** for complex visualizations - they're called during rendering
|
|
2. **Set `show_leaf_name = False`** when using custom name faces
|
|
3. **Use aligned position** for columnar data at leaf level
|
|
4. **Choose appropriate units**: pixels for screen, mm/inches for print
|
|
5. **Use vector formats (PDF/SVG)** for publications
|
|
6. **Precompute styling** when possible - layout functions should be fast
|
|
7. **Test interactively** with `show()` before rendering to file
|
|
8. **Use NodeStyle for permanent** changes, layout functions for rendering-time changes
|
|
9. **Align faces in columns** for clean, organized appearance
|
|
10. **Add legends** to explain colors and symbols used
|