skills/etetoolkit/references/visualization.md
ETE 4 has two drawing systems:
Do not mix their layout or face classes. SmartView classes live under
ete4.smartview; Qt classes live under ete4.treeview.
Use SmartView when:
Use Qt treeview when:
Interactive SmartView is included in the base package:
uv pip install "ete4==4.4.0"
Static SmartView screenshots use Selenium:
uv pip install "ete4[render-sm]==4.4.0"
Qt rendering uses PyQt6:
uv pip install "ete4[treeview]==4.4.0"
from pathlib import Path
from ete4 import Tree
with Path("tree.nw").open(encoding="utf-8") as handle:
tree = Tree(handle, parser=1)
tree.explore()
With no layouts argument, ETE applies BASIC_LAYOUT, which displays leaf
names, branch lengths, and support.
explore() returns immediately. A standalone script must keep the process
alive, for example with input(), or use keep_server=True. The bundled
visualization helper waits and stops the server cleanly.
In Jupyter/IPython, SmartView is still browser-based; the current documentation does not provide an inline SmartView widget. Keep the kernel alive and open the served URL.
ete4 explore -t tree.nw --src_tree_format 1
Inspect the active release's full options:
ete4 explore --help
The CLI supports basic source-tree selection and parser options. Although ETE
4.4.0 help exposes --face, its handler does not apply that argument; use
Python Layout objects for customization.
tree.explore(
host="127.0.0.1",
port=5000,
open_browser=False,
)
Keep the default loopback binding unless remote access is intentionally secured. For a remote host, tunnel the loopback port:
ssh -L 5000:localhost:5000 user@remote-host
Then open http://localhost:5000 locally. Do not expose an unauthenticated
explorer on 0.0.0.0 to an untrusted network.
A SmartView Layout combines:
draw_tree(tree): tree-wide style and header/legend facesdraw_node(node[, collapsed]): styles and faces for individual nodesname: GUI identifiercache_size: memoization control for node drawingfrom ete4 import Tree
from ete4.smartview import Layout, PropFace, TextFace
tree = Tree("((A:1,B:1)95:0.2,C:1);", parser="support")
def draw_tree(_tree):
yield {
"shape": "circular",
"node-height-min": 8,
"content-height-min": 4,
}
yield TextFace(
"Example phylogeny",
fs_min=8,
fs_max=22,
position="header",
)
def draw_node(node):
if node.is_leaf:
yield PropFace(
"name",
fs_min=4,
fs_max=16,
position="right",
)
return
if node.support is not None:
yield TextFace(
f"{node.support:g}",
fs_min=3,
fs_max=12,
style={"fill": "#555"},
position="top",
)
layout = Layout(
"circular labels and support",
draw_tree=draw_tree,
draw_node=draw_node,
)
tree.explore(layouts=[layout])
Support values may be fractions or percentages. Normalize only after checking the source's convention:
from ete4 import Tree
from ete4.smartview import Layout, PropFace
tree = Tree("((A:1,B:1)95:0.2,C:1);", parser="support")
def support_fraction(value):
if value is None:
return None
return value / 100 if value > 1 else value
def draw_node(node):
if node.is_leaf:
yield PropFace("name", position="right")
support = support_fraction(node.support)
if support is None:
color = "#888"
elif support >= 0.9:
color = "#1b7837"
elif support >= 0.7:
color = "#e08214"
else:
color = "#b2182b"
yield {
"dot": {
"shape": "circle",
"radius": 5,
"fill": color,
}
}
layout = Layout("support colors", draw_node=draw_node)
tree.explore(layouts=[layout])
Common tree-wide keys:
shape: "rectangular" or "circular"radius: circular layout radiusangle-start, angle-end, angle-span: circular extentnode-height-min: collapse threshold in pixelscontent-height-min: minimum height before faces appearcollapsed: collapsed-node styleshow-popup-props, hide-popup-props: property disclosureis-leaf-fn: dynamic terminal-node rulebox, dot, hz-line, vt-line: default CSS-like stylesExample:
tree_style = {
"shape": "circular",
"angle-start": -180,
"angle-span": 180,
"node-height-min": 10,
"collapsed": {
"shape": "outline",
"fill-opacity": 0.6,
},
"show-popup-props": ["name", "dist", "support", "group"],
}
layout = Layout("semicircle", draw_tree=tree_style)
tree.explore(layouts=[layout])
Limit popup properties when nodes contain sensitive or irrelevant metadata.
Common classes from ete4.smartview:
TextFace: literal textPropFace: one node property with optional formattingEvalTextFace: expression-derived textCircleFace, RectFace, BoxFace: shapesImageFace: imageSeqFace: molecular sequenceLegendFace: legendPositions include top, bottom, left, right, and aligned; tree-level
text can also use header.
from ete4.smartview import Layout, PropFace
def draw_node(node):
if not node.is_leaf:
return
yield PropFace("name", position="aligned", column=0)
yield PropFace("host", position="aligned", column=1)
yield PropFace("location", position="aligned", column=2)
layout = Layout("sample metadata", draw_node=draw_node)
tree.explore(layouts=[layout])
For a special collapsed representation, accept a second argument:
from ete4.smartview import Layout, TextFace
def draw_node(node, collapsed):
if node.name != "large_clade":
return
text = "large_clade (collapsed)" if collapsed else "large_clade"
return TextFace(text, position="right")
layout = Layout("collapsed label", draw_node=draw_node)
tree.explore(layouts=[layout])
Use collapse thresholds instead of trying to render every face for tens of thousands of leaves.
from ete4 import Tree
from ete4.smartview import Layout, PropFace
tree = Tree("((A:1,B:1),C:1);")
def draw_node(node):
if node.is_leaf:
return PropFace("name", position="right")
layout = Layout("leaf labels", draw_node=draw_node)
tree.render_sm(
"tree.png",
layouts=[layout],
w=1200,
h=800,
)
In ETE 4.4.0, render_sm() captures PNG screenshot data. Give the output a
.png suffix. Supplying .svg or .pdf does not convert the screenshot to a
vector format.
Static SmartView rendering needs a browser usable by Selenium. If browser discovery fails, install a compatible Chrome/Chromium browser or use Qt treeview.
The interactive SmartView browser can download its current view as SVG or PNG
and export Newick. That browser action is distinct from render_sm(), which is
PNG-only and has no PDF mode.
Treeview classes are not top-level ETE 4 imports.
from ete4 import Tree
from ete4.treeview import NodeStyle, TextFace, TreeStyle
tree = Tree("((A:1,B:1)95:0.2,C:1);", parser="support")
for node in tree.traverse():
style = NodeStyle()
style["size"] = 6 if node.is_leaf else 4
style["fgcolor"] = "navy" if node.is_leaf else "gray"
node.set_style(style)
tree_style = TreeStyle()
tree_style.show_leaf_name = True
tree_style.show_branch_support = True
tree_style.show_scale = True
tree_style.title.add_face(
TextFace("Example phylogeny", fsize=18, bold=True),
column=0,
)
tree.render(
"tree.svg",
w=180,
units="mm",
tree_style=tree_style,
)
tree.render(
"tree.pdf",
w=180,
units="mm",
tree_style=tree_style,
)
tree.render(
"tree.png",
w=2400,
units="px",
dpi=300,
tree_style=tree_style,
)
Qt treeview still uses TreeStyle, NodeStyle, and Qt face classes, but ETE 4
predicates remain properties:
if node.is_leaf:
...
not:
if node.is_leaf():
...
tree_style = TreeStyle()
tree_style.mode = "c"
tree_style.arc_start = -180
tree_style.arc_span = 180
tree.render("semicircle.svg", tree_style=tree_style)
On a headless Linux host, a common first attempt is:
QT_QPA_PLATFORM=offscreen python render_tree.py
If the platform plugin or required shared libraries are unavailable, use SmartView PNG, a container with the Qt runtime, or render on a workstation.
Interactive SmartView:
uv run --with "ete4==4.4.0" python scripts/quick_visualize.py \
tree.nw --parser 1
SmartView PNG:
uv run --with "ete4[render-sm]==4.4.0" python scripts/quick_visualize.py \
tree.nw tree.png \
--parser support \
--mode circular \
--show-support \
--color-by-support \
--title "Maximum-likelihood tree"
Qt SVG or PDF:
uv run --with "ete4[treeview]==4.4.0" python scripts/quick_visualize.py \
tree.nw tree.svg \
--parser 1 \
--engine treeview \
--mode rectangular \
--title "Species tree"
The script's auto engine chooses SmartView for interactive use and PNG,
and Qt treeview for PDF/SVG.
ImportError for SmartView static renderinguv pip install "ete4[render-sm]==4.4.0"
ImportError for Qt classesUse:
from ete4.treeview import NodeStyle, TreeStyle
after:
uv pip install "ete4[treeview]==4.4.0"
The likely cause is the parser. Re-read using parser="name" or
parser="support" as appropriate and inspect:
print(tree.to_str(props=["name", "dist", "support"], compact=True))
SmartView adaptively collapses branches below node-height-min. Zoom in or
lower the threshold in a layout.