diff --git a/ete4/smartview/explorer.py b/ete4/smartview/explorer.py index ffe57b6e..06beb4f3 100755 --- a/ete4/smartview/explorer.py +++ b/ete4/smartview/explorer.py @@ -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 @@ -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... @@ -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(): @@ -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 diff --git a/ete4/smartview/static/js/upload.js b/ete4/smartview/static/js/upload.js index d8f26bf3..225e349e 100644 --- a/ete4/smartview/static/js/upload.js +++ b/ete4/smartview/static/js/upload.js @@ -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()); @@ -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(); } @@ -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; }); diff --git a/ete4/smartview/static/upload.css b/ete4/smartview/static/upload.css index 2e1b4c37..5fbe7a8f 100644 --- a/ete4/smartview/static/upload.css +++ b/ete4/smartview/static/upload.css @@ -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 { @@ -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 { diff --git a/ete4/smartview/static/upload.html b/ete4/smartview/static/upload.html index 42bca822..88eef429 100644 --- a/ete4/smartview/static/upload.html +++ b/ete4/smartview/static/upload.html @@ -12,16 +12,20 @@
This is an online tool for exploring trees. It is mainly used to explore -phylogenetic trees, -but it can be used for any tree as long as it is written in the -newick format.
+This is a tool for exploring trees. It is mainly used to explore +phylogenetic +trees, but it can be used for any kind of tree.
You can explore the pre-loaded trees, or add your own trees for exploration by uploading them from this page.
+The tree can be written in
+the newick
+format or a depth-indented listing (like the output of
+the tree
program).
When using the interactive gui for exploration, press F1 or the help button in the viewer for instructions.
@@ -33,8 +37,8 @@