forked from plotly/dash-cytoscape
-
Notifications
You must be signed in to change notification settings - Fork 0
/
usage-phylogeny.py
204 lines (166 loc) · 5.67 KB
/
usage-phylogeny.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# pylint: disable=W0621
import math
import sys
import dash
from dash.dependencies import Input, Output
import dash_cytoscape as cyto
import dash_html_components as html
try:
from Bio import Phylo
except ModuleNotFoundError as e:
print(e,
"Please make sure biopython is installed correctly before running this example.")
sys.exit(1)
def generate_elements(tree, xlen=30, ylen=30, grabbable=False):
def get_col_positions(tree, column_width=80):
"""Create a mapping of each clade to its column position."""
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
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
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):
# 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
nodes.append(cy_support_node)
edges.extend([cy_support_edge, cy_edge])
add_to_elements(child, child_id)
col_positions = get_col_positions(tree)
row_positions = get_row_positions(tree)
nodes = []
edges = []
add_to_elements(tree.clade, 'r')
return nodes, edges
# Define elements, stylesheet and layout
tree = Phylo.read('data/apaf.xml', 'phyloxml')
nodes, edges = generate_elements(tree)
elements = nodes + edges
layout = {'name': 'preset'}
stylesheet = [
{
'selector': '.nonterminal',
'style': {
'label': 'data(confidence)',
'background-opacity': 0,
"text-halign": "left",
"text-valign": "top",
}
},
{
'selector': '.support',
'style': {'background-opacity': 0}
},
{
'selector': 'edge',
'style': {
"source-endpoint": "inside-to-node",
"target-endpoint": "inside-to-node",
}
},
{
'selector': '.terminal',
'style': {
'label': 'data(name)',
'width': 10,
'height': 10,
"text-valign": "center",
"text-halign": "right",
'background-color': '#222222'
}
}
]
# Start the app
app = dash.Dash(__name__)
server = app.server
app.layout = html.Div([
cyto.Cytoscape(
id='cytoscape',
elements=elements,
stylesheet=stylesheet,
layout=layout,
style={
'height': '95vh',
'width': '100%'
}
)
])
@app.callback(Output('cytoscape', 'stylesheet'),
[Input('cytoscape', 'mouseoverEdgeData')])
def color_children(edgeData):
if edgeData is None:
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)