Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions TODO.md

This file was deleted.

161 changes: 127 additions & 34 deletions demos/usage-phylogeny.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import math

import dash_cytoscape
import dash
from dash.dependencies import Input, Output
Expand All @@ -7,52 +9,122 @@
try:
from Bio import Phylo
except ModuleNotFoundError as e:
print(e, "Please make sure you have biopython installed correctly before running this example.")
print(e,
"Please make sure biopython is installed correctly before running this example.")
exit(1)


def generate_elements(tree):
elements = []

def _add_to_elements(clade, clade_id):
def generate_elements(tree, xlen=30, ylen=30, grabbable=False):
def get_col_positions(tree, column_width=80):
taxa = tree.get_terminals()

# Some constants for the drawing calculations
max_label_width = max(len(str(taxon)) for taxon in taxa)
drawing_width = column_width - max_label_width - 1

"""Create a mapping of each clade to its column position."""
depths = tree.depths()
# If there are no branch lengths, assume unit branch lengths
if not max(depths.values()):
depths = tree.depths(unit_branch_lengths=True)
# Potential drawing overflow due to rounding -- 1 char per tree layer
fudge_margin = int(math.ceil(math.log(len(taxa), 2)))
cols_per_branch_unit = ((drawing_width - fudge_margin) /
float(max(depths.values())))
return dict((clade, int(blen * cols_per_branch_unit + 1.0))
for clade, blen in depths.items())

def get_row_positions(tree):
taxa = tree.get_terminals()
positions = dict((taxon, 2 * idx) for idx, taxon in enumerate(taxa))

def calc_row(clade):
for subclade in clade:
if subclade not in positions:
calc_row(subclade)
positions[clade] = ((positions[clade.clades[0]] +
positions[clade.clades[-1]]) // 2)

calc_row(tree.root)
return positions

def add_to_elements(clade, clade_id):
children = clade.clades

cy_source = {"data": {"id": clade_id}, 'classes': 'nonterminal'}
elements.append(cy_source)
pos_x = col_positions[clade] * xlen
pos_y = row_positions[clade] * ylen

cy_source = {
"data": {"id": clade_id},
'position': {'x': pos_x, 'y': pos_y},
'classes': 'nonterminal',
'grabbable': grabbable
}
nodes.append(cy_source)

if clade.is_terminal():
cy_source['data']['name'] = clade.name
cy_source['classes'] = 'terminal'

for n, child in enumerate(children, 1):
child_id = len(elements) + n

cy_edge = {'data': {
'source': clade_id,
'target': child_id,
'length': clade.branch_length
}}
for n, child in enumerate(children):
# The "support" node is on the same column as the parent clade,
# and on the same row as the child clade. It is used to create the
# 90 degree angle between the parent and the children.
# Edge config: parent -> support -> child

support_id = clade_id + 's' + str(n)
child_id = clade_id + 'c' + str(n)
pos_y_child = row_positions[child] * ylen

cy_support_node = {
'data': {'id': support_id},
'position': {'x': pos_x, 'y': pos_y_child},
'grabbable': grabbable,
'classes': 'support'
}

cy_support_edge = {
'data': {
'source': clade_id,
'target': support_id,
'sourceCladeId': clade_id
},
}

cy_edge = {
'data': {
'source': support_id,
'target': child_id,
'length': clade.branch_length,
'sourceCladeId': clade_id
},
}

if clade.confidence and clade.confidence.value:
cy_source['data']['confidence'] = clade.confidence.value

elements.extend([cy_edge])
nodes.append(cy_support_node)
edges.extend([cy_support_edge, cy_edge])

_add_to_elements(child, child_id)
add_to_elements(child, child_id)

_add_to_elements(tree.clade, 0)
col_positions = get_col_positions(tree)
row_positions = get_row_positions(tree)

return elements
nodes = []
edges = []

add_to_elements(tree.clade, 'r')

return nodes, edges


# Define elements, stylesheet and layout
tree = Phylo.read('data/apaf.xml', 'phyloxml')
elements = generate_elements(tree)
nodes, edges = generate_elements(tree)
elements = nodes + edges

layout = {
'name': 'breadthfirst',
'directed': True
}
layout = {'name': 'preset'}

stylesheet = [
{
Expand All @@ -64,36 +136,36 @@ def _add_to_elements(clade, clade_id):
"text-valign": "top",
}
},
{
'selector': '.support',
'style': {'background-opacity': 0}
},
{
'selector': 'edge',
'style': {
"source-endpoint": "outside-to-node",
"source-endpoint": "inside-to-node",
"target-endpoint": "inside-to-node",
}
},
{
'selector': '.terminal',
'style': {
'label': 'data(name)',
"shape": "roundrectangle",
"width": 115,
"height": 25,
'width': 10,
'height': 10,
"text-valign": "center",
'background-color': 'white',
"border-width": 1.5,
"border-style": "solid",
"border-opacity": 1,
"text-halign": "right",
'background-color': '#222222'
}
}
]


# Start the app
app = dash.Dash(__name__)

app.scripts.config.serve_locally = True
app.css.config.serve_locally = True


app.layout = html.Div([
dash_cytoscape.Cytoscape(
id='cytoscape',
Expand All @@ -108,5 +180,26 @@ def _add_to_elements(clade, clade_id):
])


@app.callback(Output('cytoscape', 'stylesheet'),
[Input('cytoscape', 'mouseoverEdgeData')])
def color_children(edgeData):
if not edgeData:
return stylesheet

if 's' in edgeData['source']:
val = edgeData['source'].split('s')[0]
else:
val = edgeData['source']

children_style = [{
'selector': f'edge[source *= "{val}"]',
'style': {
'line-color': 'blue'
}
}]

return stylesheet + children_style


if __name__ == '__main__':
app.run_server(debug=True)