Skip to content

Commit

Permalink
Merge pull request #105 from dengzq1234/desktop_v2
Browse files Browse the repository at this point in the history
Desktop v2
  • Loading branch information
dengzq1234 authored Dec 18, 2024
2 parents f3be338 + 6ee1904 commit f533410
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 40 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,12 @@ TreeProfiler requires
- webdriver_manager
- pyvirtualdisplay

### Quick install via pip
### Quick Install Environment
```
# create environment
conda create -n treeprofiler python=3.10
conda activate treeprofiler
# Install ETE Toolkit v4
pip install --force-reinstall https://github.com/etetoolkit/ete/archive/ete4.zip
Expand Down
7 changes: 6 additions & 1 deletion treeprofiler/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,14 +651,19 @@ def explore_tree(treename):
# Start the ete exploration thread
start_explore_thread(t, treename, current_layouts, current_props)

# Before rendering the template, convert to JSON
#layouts_json = json.dumps(tree_info['layouts'])
#layouts_metadata_json = json.dumps(layouts_metadata)

# Render template
return template(
'explore_tree_v2',
treename=treename,
tree_info=tree_info,
selected_props=current_props,
color_schemes=continuous_colormaps,
layouts_metadata=layouts_metadata
layouts_metadata=layouts_metadata,
#layouts_metadata_json = json.dumps(layouts_metadata)
)

def get_colormap_hex_colors(colormap_name, num_colors):
Expand Down
4 changes: 2 additions & 2 deletions treeprofiler/layouts/text_layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ def set_node_style(self, node):
tooltip += f'<br>{self.prop}: {prop_text}<br>'

if self.color_dict:
color = self.color_dict.get(str(prop_text),"")
color = self.color_dict.get(prop_text,"")
prop_face = RectFace(width=self.width, height=self.height, color=color, \
padding_x=self.padding_x , padding_y=self.padding_y, tooltip=tooltip)
node.add_face(prop_face, column=self.column, position="aligned")
Expand Down Expand Up @@ -458,7 +458,7 @@ def set_node_style(self, node):
tooltip += f'<br>{self.prop}: {prop_text}<br>'

if self.color_dict:
color = self.color_dict.get(str(prop_text), self.absence_color)
color = self.color_dict.get(prop_text, self.absence_color)
align_link_face = AlignLinkFace(width=self.width*2, height=None,
stroke_color=color, stroke_width=2, line_type=0, opacity=0.7)
node.sm_style["bgcolor"] = color
Expand Down
24 changes: 18 additions & 6 deletions treeprofiler/src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import matplotlib.colors as mcolors
import numpy as np
from scipy import stats
import numbers
import random
import colorsys
import operator
Expand Down Expand Up @@ -270,7 +271,23 @@ def get_internal_parser(internal_parser="name"):
return 1
elif internal_parser == "support":
return 0


def get_prop2type(node):
output = {}
prop2value = node.props
if '_speciesFunction' in prop2value:
del prop2value['_speciesFunction']

for prop, value in prop2value.items():
if value != 'NaN':
if isinstance(value, numbers.Number):
output[prop] = float
elif type(value) == list:
output[prop] = list
else:
output[prop] = str
return output

def ete4_parse(newick, internal_parser="name"):
tree = PhyloTree(newick, parser=get_internal_parser(internal_parser))
# Correct 0-dist trees
Expand Down Expand Up @@ -349,11 +366,6 @@ def conditional_prune(tree, conditions_input, prop2type):
# n.dist = 1
return tree


#array = [n.props.get(prop) if n.props.get(prop) else 'NaN' for n in nodes]
array = [n.props.get(prop) for n in nodes if n.props.get(prop) ]
return array

# def _tree_prop_array(node, prop, leaf_only=False, numeric=False, list_type=False):
# array = []
# sep = '||'
Expand Down
33 changes: 25 additions & 8 deletions treeprofiler/tree_annotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,14 @@ def run(args):
# parsing tree
try:
tree, eteformat_flag = utils.validate_tree(args.tree, args.input_type, args.internal)
# get tree orignal properties
for path, node in tree.iter_prepostorder():
prop2type.update(utils.get_prop2type(node))
del prop2type['name']
del prop2type['dist']
if 'support' in prop2type:
del prop2type['support']

except utils.TreeFormatError as e:
logger.error(e)
sys.exit(1)
Expand All @@ -750,8 +758,9 @@ def run(args):
logger.info(f'start parsing...')
# parsing metadata
if args.metadata: # make a series of metadatas
metadata_dict, node_props, columns, prop2type = parse_csv(args.metadata, delimiter=args.metadata_sep, \
metadata_dict, node_props, columns, metadata_prop2type = parse_csv(args.metadata, delimiter=args.metadata_sep, \
no_headers=args.no_headers, duplicate=args.duplicate)
prop2type.update(metadata_prop2type)
else: # annotated_tree
node_props=[]
columns = {}
Expand Down Expand Up @@ -919,16 +928,23 @@ def run(args):
list2str = list_sep.join(node.props.get(key))
node.add_prop(key, list2str)
avail_props = list(prop2type.keys())
del avail_props[avail_props.index('name')]

#del avail_props[avail_props.index('name')]
del avail_props[avail_props.index('dist')]
if 'support' in avail_props:
del avail_props[avail_props.index('support')]

annotated_tree.write(outfile=os.path.join(args.outdir, out_newick), props=avail_props,
parser=utils.get_internal_parser(args.internal), format_root_node=True)

if args.stdout:
print(annotated_tree.write(props=avail_props, parser=utils.get_internal_parser(args.internal), format_root_node=True))
avail_props = list(prop2type.keys())
#del avail_props[avail_props.index('name')]
del avail_props[avail_props.index('dist')]
if 'support' in avail_props:
del avail_props[avail_props.index('support')]
print(annotated_tree.write(props=avail_props, parser=utils.get_internal_parser(args.internal),
format_root_node=True))

# if args.outtsv:
# tree2table(annotated_tree, internal_node=True, outfile=args.outtsv)
Expand Down Expand Up @@ -1656,10 +1672,11 @@ def merge_dictionaries(dict_ranks, dict_names):
if n.props.get('rank') and n.props.get('rank') != 'Unknown':
rank2values[n.props.get('rank')].append(n.props.get('sci_name',''))

# if n.name:
# pass
# else:
# n.name = n.props.get("sci_name", "")
# TODO assign internal node as sci_name, ATTENTION of potential bug
if n.name:
pass
else:
n.name = n.props.get("sci_name", "")

return tree, rank2values

Expand Down
20 changes: 1 addition & 19 deletions treeprofiler/tree_plot.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python
import numbers
import math
import sys
import os
Expand Down Expand Up @@ -393,7 +392,7 @@ def run(args):

if eteformat_flag:
for path, node in tree.iter_prepostorder():
prop2type.update(get_prop2type(node))
prop2type.update(utils.get_prop2type(node))

if args.collapsed_by: # need to be wrap with quotes
condition_strings = args.collapsed_by
Expand Down Expand Up @@ -1549,7 +1548,6 @@ def process_prop_values(tree, prop):

#minval, maxval = all_prop_values.min(), all_prop_values.max()
if bubble_range:
print(bubble_range)
abs_maxval = np.abs(bubble_range).max()
max_val = bubble_range[1]
min_val = bubble_range[0]
Expand Down Expand Up @@ -1771,22 +1769,6 @@ def get_heatmap_matrix_layouts(layout_name, numerical_props, norm_method, intern
layouts.append(matrix_layout)
return layouts, level

def get_prop2type(node):
output = {}
prop2value = node.props
if '_speciesFunction' in prop2value:
del prop2value['_speciesFunction']

for prop, value in prop2value.items():
if value != 'NaN':
if isinstance(value, numbers.Number):
output[prop] = float
elif type(value) == list:
output[prop] = list
else:
output[prop] = str
return output

def categorical2matrix(tree, profiling_props, dtype=str, color_config=None):
"""
Input:
Expand Down
106 changes: 103 additions & 3 deletions treeprofiler/views/explore_tree_v2.html
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ <h6 class="mb-0 fw-bold">Manage Loaded Layouts</h6>
<button class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil-square me-1"></i>Edit
</button>
<button class="btn btn-sm btn-outline-danger">
<button class="btn btn-sm btn-outline-danger delete-layout-btn">
<i class="bi bi-trash-fill me-1"></i>Delete
</button>
</div>
Expand All @@ -571,10 +571,12 @@ <h6 class="mb-0 fw-bold">Manage Loaded Layouts</h6>
% end
</tbody>
</table>
<!-- Hidden input to store the current layouts_metadata -->
<input id="layoutsMetadataInput" value="{{ layouts_metadata }}">
</div>
<!-- Apply Changes Button -->
<div class="text-center mt-4">
<button class="btn btn-success w-100 py-2 fw-bold">
<button id="applyChangesBtn" class="btn btn-success w-100 py-2 fw-bold">
<i class="bi bi-check-circle-fill me-2"></i>Apply Changes
</button>
</div>
Expand Down Expand Up @@ -849,8 +851,106 @@ <h1>Explore Tree: {{treename}}</h1>
})
.catch(error => console.error('Error:', error));
});


// update changes
// document.addEventListener('DOMContentLoaded', () => {
// // Event delegation for the delete button
// document.getElementById('activeLayoutsTable').addEventListener('click', function(event) {
// const deleteBtn = event.target.closest('.delete-layout-btn');
// if (deleteBtn) {
// const row = deleteBtn.closest('tr');

// // Clear all cells' content except the row structure
// row.querySelectorAll('td').forEach(cell => {
// cell.innerHTML = '';
// });
// }
// });

// // Handle Apply Changes
// document.getElementById('applyChangesBtn').addEventListener('click', function() {
// const remainingLayouts = [];
// console.log('Applying changes...', remainingLayouts)
// // Collect rows with content
// document.querySelectorAll('#activeLayoutsTable tr').forEach(row => {
// const layoutName = row.querySelector('td:nth-child(1)').innerText.trim();
// const appliedProps = Array.from(row.querySelectorAll('td:nth-child(2) li'))
// .map(prop => prop.innerText.trim());

// if (layoutName) { // Only include rows that are not cleared
// remainingLayouts.push({
// layout_name: layoutName,
// applied_props: appliedProps
// });
// }
// });

// // Send the remaining layouts back to the backend
// fetch('', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ action: 'apply_changes', layouts: remainingLayouts })
// })
// .then(response => response.text())
// .then(data => {
// alert('Changes applied successfully!');
// location.reload(); // Optionally reload the page
// })
// .catch(error => console.error('Error:', error));
// });
// });
</script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const tableBody = document.getElementById('activeLayoutsTable');
const layoutsMetadata = document.getElementById('layoutsMetadataInput');

// Function to update the hidden input field
function updateHiddenInput() {
layoutsMetadataInput.value = JSON.stringify(layoutsMetadata);
}

// Function to find and remove layout metadata by name
function removeLayoutMetadata(layoutName) {
layoutsMetadata = layoutsMetadata.filter(layout => layout.layout_name !== layoutName);
updateHiddenInput();
}

// Handle Delete Button Click
tableBody.addEventListener('click', function (e) {
if (e.target.closest('.delete-layout-btn')) {
const row = e.target.closest('tr'); // Find the table row
const layoutName = row.querySelector('td:nth-child(1)').innerText.trim(); // Get layout name

// Remove the row from the table
row.remove();

// Remove the corresponding layout from the metadata
removeLayoutMetadata(layoutName);
}
});

// Handle Apply Changes Button
document.getElementById('applyChangesBtn').addEventListener('click', function () {
// Send the updated layoutsMetadata back to the backend
fetch(window.location.pathname, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
updated_metadata: layoutsMetadataInput.value
})
})
.then(response => response.text())
.then(data => {
alert("Layouts updated successfully!");
location.reload();
})
.catch(error => console.error('Error:', error));
});
});
</script>


<script>
// this is for layout toggle settings
$(document).ready(function (){
Expand Down

0 comments on commit f533410

Please sign in to comment.