Skip to content

Commit 3204154

Browse files
authored
Merge branch 'master' into dash-brain-viewer
2 parents 3b80c34 + bd0c45b commit 3204154

File tree

381 files changed

+13534
-229972
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

381 files changed

+13534
-229972
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
venv
2+
.vscode
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: gunicorn --pythonpath apps/dash-cytoscape-phylogeny app:server
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Dash Cytoscape Phylogeny
2+
3+
## About this app
4+
5+
This is a demo of the Dash interactive Python framework developed by Plotly.
6+
7+
## How to run this app
8+
9+
To run this app first clone repository and then open a terminal to the app folder.
10+
11+
```
12+
git clone https://github.com/plotly/dash-sample-apps.git
13+
cd dash-sample-apps/apps/dash-cytoscape
14+
```
15+
16+
Create and activate a new virtual environment (recommended) by running
17+
the following:
18+
19+
On Windows
20+
21+
```
22+
virtualenv venv
23+
\venv\scripts\activate
24+
```
25+
26+
Or if using linux
27+
28+
```bash
29+
python3 -m venv myvenv
30+
source myvenv/bin/activate
31+
```
32+
33+
Install the requirements:
34+
35+
```
36+
pip install -r requirements.txt
37+
```
38+
Run the app:
39+
40+
```
41+
python app.py
42+
```
43+
You can run the app on your browser at http://127.0.0.1:8050
44+
45+
46+
## Screenshots
47+
48+
![demo.png](demo.png)
49+
50+
## Resources
51+
52+
To learn more about Dash, please visit [documentation](https://plot.ly/dash).
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import math
2+
import dash
3+
from dash.dependencies import Input, Output
4+
import dash_cytoscape as cyto
5+
import dash_html_components as html
6+
import pathlib
7+
8+
from Bio import Phylo
9+
10+
app = dash.Dash(
11+
__name__, meta_tags=[{"name": "viewport", "content": "width=device-width"}]
12+
)
13+
server = app.server
14+
15+
16+
def generate_elements(tree, xlen=30, ylen=30, grabbable=False):
17+
def get_col_positions(tree, column_width=80):
18+
"""Create a mapping of each clade to its column position."""
19+
taxa = tree.get_terminals()
20+
21+
# Some constants for the drawing calculations
22+
max_label_width = max(len(str(taxon)) for taxon in taxa)
23+
drawing_width = column_width - max_label_width - 1
24+
25+
depths = tree.depths()
26+
# If there are no branch lengths, assume unit branch lengths
27+
if not max(depths.values()):
28+
depths = tree.depths(unit_branch_lengths=True)
29+
# Potential drawing overflow due to rounding -- 1 char per tree layer
30+
fudge_margin = int(math.ceil(math.log(len(taxa), 2)))
31+
cols_per_branch_unit = (drawing_width - fudge_margin) / float(
32+
max(depths.values())
33+
)
34+
return dict(
35+
(clade, int(blen * cols_per_branch_unit + 1.0))
36+
for clade, blen in depths.items()
37+
)
38+
39+
def get_row_positions(tree):
40+
taxa = tree.get_terminals()
41+
positions = dict((taxon, 2 * idx) for idx, taxon in enumerate(taxa))
42+
43+
def calc_row(clade):
44+
for subclade in clade:
45+
if subclade not in positions:
46+
calc_row(subclade)
47+
positions[clade] = (
48+
positions[clade.clades[0]] + positions[clade.clades[-1]]
49+
) // 2
50+
51+
calc_row(tree.root)
52+
return positions
53+
54+
def add_to_elements(clade, clade_id):
55+
children = clade.clades
56+
57+
pos_x = col_positions[clade] * xlen
58+
pos_y = row_positions[clade] * ylen
59+
60+
cy_source = {
61+
"data": {"id": clade_id},
62+
"position": {"x": pos_x, "y": pos_y},
63+
"classes": "nonterminal",
64+
"grabbable": grabbable,
65+
}
66+
nodes.append(cy_source)
67+
68+
if clade.is_terminal():
69+
cy_source["data"]["name"] = clade.name
70+
cy_source["classes"] = "terminal"
71+
72+
for n, child in enumerate(children):
73+
# The "support" node is on the same column as the parent clade,
74+
# and on the same row as the child clade. It is used to create the
75+
# 90 degree angle between the parent and the children.
76+
# Edge config: parent -> support -> child
77+
78+
support_id = clade_id + "s" + str(n)
79+
child_id = clade_id + "c" + str(n)
80+
pos_y_child = row_positions[child] * ylen
81+
82+
cy_support_node = {
83+
"data": {"id": support_id},
84+
"position": {"x": pos_x, "y": pos_y_child},
85+
"grabbable": grabbable,
86+
"classes": "support",
87+
}
88+
89+
cy_support_edge = {
90+
"data": {
91+
"source": clade_id,
92+
"target": support_id,
93+
"sourceCladeId": clade_id,
94+
}
95+
}
96+
97+
cy_edge = {
98+
"data": {
99+
"source": support_id,
100+
"target": child_id,
101+
"length": clade.branch_length,
102+
"sourceCladeId": clade_id,
103+
}
104+
}
105+
106+
if clade.confidence and clade.confidence.value:
107+
cy_source["data"]["confidence"] = clade.confidence.value
108+
109+
nodes.append(cy_support_node)
110+
edges.extend([cy_support_edge, cy_edge])
111+
112+
add_to_elements(child, child_id)
113+
114+
col_positions = get_col_positions(tree)
115+
row_positions = get_row_positions(tree)
116+
117+
nodes = []
118+
edges = []
119+
120+
add_to_elements(tree.clade, "r")
121+
122+
return nodes, edges
123+
124+
125+
PATH = pathlib.Path(__file__).parent
126+
DATA_PATH = PATH.joinpath("data").resolve()
127+
128+
# Define elements, stylesheet and layout
129+
tree = Phylo.read(DATA_PATH.joinpath("apaf.xml"), "phyloxml")
130+
nodes, edges = generate_elements(tree)
131+
elements = nodes + edges
132+
133+
134+
stylesheet = [
135+
{
136+
"selector": ".nonterminal",
137+
"style": {
138+
"label": "data(confidence)",
139+
"background-opacity": 0,
140+
"text-halign": "left",
141+
"text-valign": "top",
142+
},
143+
},
144+
{"selector": ".support", "style": {"background-opacity": 0}},
145+
{
146+
"selector": "edge",
147+
"style": {
148+
"source-endpoint": "inside-to-node",
149+
"target-endpoint": "inside-to-node",
150+
},
151+
},
152+
{
153+
"selector": ".terminal",
154+
"style": {
155+
"label": "data(name)",
156+
"width": 10,
157+
"height": 10,
158+
"text-valign": "center",
159+
"text-halign": "right",
160+
"background-color": "#222222",
161+
},
162+
},
163+
]
164+
165+
app.layout = html.Div(
166+
[
167+
html.Img(className="logo", src=app.get_asset_url("dash-logo.png")),
168+
html.Div(
169+
className="header",
170+
children=[
171+
html.Div(
172+
className="div-info",
173+
children=[
174+
html.H2(className="title", children="Cytoscape Phylogeny"),
175+
html.P(
176+
"""
177+
Dash Cytoscape is a graph visualization component for creating easily customizable,
178+
high-performance interactive, and web-based networks.
179+
"""
180+
),
181+
html.A(
182+
children=html.Button("Learn More", className="button"),
183+
href="https://dash.plot.ly/cytoscape",
184+
target="_blank",
185+
),
186+
],
187+
),
188+
html.H4("Phylogeny"),
189+
cyto.Cytoscape(
190+
id="cytoscape",
191+
className="cytoscape",
192+
elements=elements,
193+
stylesheet=stylesheet,
194+
layout={"name": "preset", "fit": True, "animate": True},
195+
minZoom=0.25,
196+
),
197+
],
198+
),
199+
]
200+
)
201+
202+
203+
@app.callback(
204+
Output("cytoscape", "stylesheet"), [Input("cytoscape", "mouseoverEdgeData")]
205+
)
206+
def color_children(edgeData):
207+
if edgeData is None:
208+
return stylesheet
209+
210+
if "s" in edgeData["source"]:
211+
val = edgeData["source"].split("s")[0]
212+
else:
213+
val = edgeData["source"]
214+
215+
children_style = [
216+
{"selector": f'edge[source *= "{val}"]', "style": {"line-color": "#3ed6d2"}}
217+
]
218+
219+
return stylesheet + children_style
220+
221+
222+
if __name__ == "__main__":
223+
app.run_server(debug=True)

0 commit comments

Comments
 (0)