Skip to content

Commit

Permalink
Improve the upload page and add option to parse an indented tree.
Browse files Browse the repository at this point in the history
Now upload.html has better wording and is better looking.

Also, we can upload a tree that looks like:

  root
    node1
      leaf11
      leaf12
    node2

and so on.
  • Loading branch information
jordibc committed Jan 22, 2025
1 parent 19b5b60 commit 22bd8ed
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 52 deletions.
36 changes: 20 additions & 16 deletions ete4/smartview/explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
DIR_BIN = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.dirname(DIR_BIN)) # so we can import ete w/o install

from ete4 import newick, nexus, operations as ops, treematcher as tm
from ete4 import newick, nexus, indent, operations as ops, treematcher as tm
from . import draw
from .layout import Layout, BASIC_LAYOUT, update_style

Expand Down Expand Up @@ -568,16 +568,16 @@ def get_topological_search(pattern):
def add_trees_from_request():
"""Add trees to the global var g_trees and return a dict of {name: id}."""
try:
if request.content_type.startswith('application/json'):
if request.content_type.startswith('application/json'): # a POST
trees = [req_json()] # we have only one tree
parser = newick.PARSER_DEFAULT
else:
parser = 'name'
else: # the request comes from a form (e.g., from upload.html)
trees = get_trees_from_form()
parser = get_parser(request.forms['internal'])
parser = request.forms['parser']

names = {}
for tree in trees:
t = newick.loads(tree['newick'], parser)
t = loads(tree['newick'], parser)
ops.update_sizes_all(t)
name = tree['name'].replace(',', '_') # "," is used for subtrees
names[name] = name # tree ids are already equal to their names...
Expand All @@ -591,10 +591,14 @@ def add_trees_from_request():
abort(400, f'malformed tree - {e}')


def get_parser(internal):
"""Return parser given the internal nodes main property interpretation."""
p = {'name': newick.NAME, 'support': newick.SUPPORT}[internal] # (()p:d);
return dict(newick.PARSER_DEFAULT, internal=[p, newick.DIST])
def loads(tree_text, parser):
"""Return tree loaded from the text using the given parser."""
if parser in ['name', 'support']:
return newick.loads(tree_text, parser)
elif parser == 'nexus':
return nexus.loads(tree_text)
elif parser == 'indent':
return indent.loads(tree_text)


def get_trees_from_form():
Expand Down Expand Up @@ -773,18 +777,18 @@ def stop_server():
if __name__ == '__main__':
parser = ArgumentParser(description=__doc__, formatter_class=fmt)
add = parser.add_argument # shortcut
add('FILE', help='file with the newick tree representation')
add('-i', '--internal', choices=['name', 'support'], default='support',
help='how to interpret the content of internal nodes')
add('-c', '--compress', action='store_true', help='send compressed data')
add('-p', '--port', type=int, help='server port number')
add('FILE', help='file with the tree representation')
add('--parser', choices=['name', 'support', 'indent'], default='support',
help='tree is newick with name/support in internal nodes, or indented')
add('--compress', action='store_true', help='send compressed data')
add('--port', type=int, help='server port number')
add('-v', '--verbose', action='store_true', help='be verbose')
args = parser.parse_args()

try:
# Read tree(s) and add them to g_trees.
for tree in get_trees_from_file(args.FILE):
t = newick.loads(tree['newick'], get_parser(args.internal))
t = loads(tree['newick'], args.parser)
ops.update_sizes_all(t)
name = tree['name'].replace(',', '_') # "," is used for subtrees
g_trees[name] = t
Expand Down
23 changes: 13 additions & 10 deletions ete4/smartview/static/js/upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ button_upload.addEventListener("click", async () => {
try {
assert(input_name.value || !check_metadata.checked, "Missing name");

const name = check_metadata.checked ?
input_name.value : hash(Date.now().toString());
const name = check_metadata.checked ? input_name.value :
hash(Date.now().toString());
const parser = radio_name.checked ? "name" :
radio_support.checked ? "support" :
radio_indent.checked ? "indent" : "undefined";

assert(!input_trees_file.disabled || !input_newick_string.disabled,
"You need to supply a newick string or select a file");
assert(!input_trees_file.disabled || !input_string.disabled,
"You need to supply a string or select a file");

const data = new FormData();
data.append("name", name);
data.append("internal", radio_name.checked ? "name" : "support");
if (!input_newick_string.disabled)
data.append("newick", input_newick_string.value.trim());
data.append("parser", parser);
if (!input_string.disabled)
data.append("newick", input_string.value.trim());
else
data.append("trees", await get_trees_file());

Expand Down Expand Up @@ -75,7 +78,7 @@ function show_uploaded_trees(resp) {
function load_example() {
radio_string.click();

input_newick_string.value = `
input_string.value = `
((((((((((('Escherichia coli_D':0.001,'Escherichia coli':0.001)Escherichia:0.001,('Escherichia dysenteriae':0.002,'Escherichia flexneri':0.001)Escherichia:0.001)Escherichia:0.001,'Escherichia fergusonii':0.005)Escherichia:0.001,'Escherichia coli_C':0.001)Escherichia:0.001,'Escherichia sp002965065':0.004)Escherichia:0.001,(((('Escherichia sp004211955':0.001,'Escherichia sp005843885':0.001)Escherichia:0.001,'Escherichia sp001660175':0.002)Escherichia:0.001,'Escherichia marmotae':0.003)Escherichia:0.001,'Escherichia sp000208585':0.003)Escherichia:0.001)Escherichia:0.001,'Escherichia albertii':0.005)Escherichia:0.016,(((('Salmonella diarizonae':0.003,'Salmonella houtenae':0.003)Salmonella:0.001,'Salmonella arizonae':0.005)Salmonella:0.001,'Salmonella enterica':0.002)Salmonella:0.003,'Salmonella bongori':0.007)Salmonella:0.009)Enterobacteriaceae:0.004,(((('Citrobacter_A amalonaticus_C':0.006,'Citrobacter_A farmeri':0.003)Citrobacter_A:0.002,'Citrobacter_A amalonaticus':0.004)Citrobacter_A:0.006,('Citrobacter_A rodentium':0.011,'Citrobacter_A sedlakii':0.006)Citrobacter_A:0.006)Citrobacter_A:0.004,'Citrobacter_C amalonaticus_A':0.017)Enterobacteriaceae:0.002)Enterobacteriaceae:0.002,'Citrobacter_B koseri':0.014)Enterobacteriaceae:0.004,((((((('Citrobacter freundii_E':0.002,'Citrobacter europaeus':0.002)Citrobacter:0.001,'Citrobacter braakii':0.003)Citrobacter:0.001,'Citrobacter freundii_A':0.003)Citrobacter:0.001,'Citrobacter freundii':0.002)Citrobacter:0.001,(('Citrobacter portucalensis_A':0.002,'Citrobacter werkmanii':0.002)Citrobacter:0.003,'Citrobacter portucalensis':0.001)Citrobacter:0.001)Citrobacter:0.002,('Citrobacter youngae':0.001,'Citrobacter sp005281345':0.003)Citrobacter:0.002)Citrobacter:0.004,(('Citrobacter gillenii':0.005,'Citrobacter sp004684345':0.004)Citrobacter:0.007,'Citrobacter murliniae':0.007)Citrobacter:0.002)Citrobacter:0.011);
`.trim();
}
Expand All @@ -86,11 +89,11 @@ window.load_example = load_example;

radio_file.addEventListener("click", () => {
input_trees_file.disabled = false;
input_newick_string.disabled = true;
input_string.disabled = true;
});

radio_string.addEventListener("click", () => {
input_newick_string.disabled = false;
input_string.disabled = false;
input_trees_file.disabled = true;
});

Expand Down
35 changes: 29 additions & 6 deletions ete4/smartview/static/upload.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,9 @@ body {
}

h1 {
font-size: 2em;
font-family: Helvetica, sans-serif;
font-weight: 200;
}

h1, p {
color: #333;
border-bottom: 3px solid #00b894;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
}

div {
Expand All @@ -26,6 +22,33 @@ div {

fieldset {
display: inline-block;
border-style: solid;
border-width: 2px;
border-color: #DDD;
border-radius: 4px;

}

button {
padding: 12px 24px;
background-color: #0ea5e9;
color: white;
border: 0;
border-radius: 6px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}

button:hover {
background-color: #0284c7;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

button:active {
transform: translateY(0);
box-shadow: none;
}

div#div_metadata {
Expand Down
51 changes: 31 additions & 20 deletions ete4/smartview/static/upload.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,20 @@

<div class="centered">

<h1>Tree Explorer</h1>
<h1>ETE Tree Explorer</h1>

<p>This is an online tool for exploring trees. It is mainly used to explore
<a href="https://en.wikipedia.org/wiki/Phylogenetic_tree">phylogenetic trees</a>,
but it can be used for any tree as long as it is written in the
<a href="https://en.wikipedia.org/wiki/Newick_format">newick format</a>.</p>
<p>This is a tool for exploring trees. It is mainly used to explore
<a href="https://en.wikipedia.org/wiki/Phylogenetic_tree">phylogenetic
trees</a>, but it can be used for any kind of tree.</p>

<p>You can <a href="/">explore the pre-loaded trees</a>, or add your
own trees for exploration by uploading them from this page.</p>

<p>The tree can be written in
the <a href="https://en.wikipedia.org/wiki/Newick_format">newick
format</a> or a depth-indented listing (like the output of
the <code>tree</code> program).</p>

<p>When using the interactive gui for exploration, press <kbd>F1</kbd>
or the help button in the viewer for instructions.</p>

Expand All @@ -33,8 +37,8 @@ <h1>Tree Explorer</h1>
From string (<a href="#" onclick="load_example(); return false;">load
an example</a>)
</label><br>
<textarea id="input_newick_string" rows="8" cols="80"
placeholder="Newick representation of the tree" disabled></textarea>
<textarea id="input_string" rows="8" cols="80"
placeholder="Newick or indented representation of the tree" disabled></textarea>
</div>

<div>
Expand All @@ -46,20 +50,27 @@ <h1>Tree Explorer</h1>
</fieldset>

<div>
<fieldset><legend>How to interpret content of internal nodes</legend>
<div>
<input type="radio" id="radio_name" name="internal" value="name" checked>
<label for="radio_name">
As node name &mdash; <tt>((name:dist)name:dist);</tt>
</label>
</div>
<fieldset><legend>How to parse</legend>
<div>
<input type="radio" id="radio_name" name="parser" value="name" checked>
<label for="radio_name">
<tt>((name:dist)<b>name</b>:dist);</tt> (newick with internal node name)
</label>
</div>

<div>
<input type="radio" id="radio_support" name="internal" value="support">
<label for="radio_support">
As node support &mdash; <tt>((name:dist)support:dist);</tt>
</label>
</div>
<div>
<input type="radio" id="radio_support" name="parser" value="support">
<label for="radio_support">
<tt>((name:dist)<b>support</b>:dist);</tt> (newick with internal node support)
</label>
</div>

<div>
<input type="radio" id="radio_indent" name="parser" value="indent">
<label for="radio_indent">
Depth-indented listing
</label>
</div>
</fieldset>
</div>

Expand Down

0 comments on commit 22bd8ed

Please sign in to comment.