|
11 | 11 | exit(1) |
12 | 12 |
|
13 | 13 |
|
14 | | -def generate_elements(tree): |
15 | | - elements = [] |
16 | | - |
17 | | - def _add_to_elements(clade, clade_id): |
| 14 | +def generate_elements(tree, xlen=30, ylen=30, grabbable=False): |
| 15 | + def get_col_positions(tree, column_width=80): |
| 16 | + taxa = tree.get_terminals() |
| 17 | + |
| 18 | + # Some constants for the drawing calculations |
| 19 | + max_label_width = max(len(str(taxon)) for taxon in taxa) |
| 20 | + drawing_width = column_width - max_label_width - 1 |
| 21 | + |
| 22 | + """Create a mapping of each clade to its column position.""" |
| 23 | + depths = tree.depths() |
| 24 | + # If there are no branch lengths, assume unit branch lengths |
| 25 | + if not max(depths.values()): |
| 26 | + depths = tree.depths(unit_branch_lengths=True) |
| 27 | + # Potential drawing overflow due to rounding -- 1 char per tree layer |
| 28 | + fudge_margin = int(math.ceil(math.log(len(taxa), 2))) |
| 29 | + cols_per_branch_unit = ((drawing_width - fudge_margin) / |
| 30 | + float(max(depths.values()))) |
| 31 | + return dict((clade, int(blen * cols_per_branch_unit + 1.0)) |
| 32 | + for clade, blen in depths.items()) |
| 33 | + |
| 34 | + def get_row_positions(tree): |
| 35 | + taxa = tree.get_terminals() |
| 36 | + positions = dict((taxon, 2 * idx) for idx, taxon in enumerate(taxa)) |
| 37 | + |
| 38 | + def calc_row(clade): |
| 39 | + for subclade in clade: |
| 40 | + if subclade not in positions: |
| 41 | + calc_row(subclade) |
| 42 | + positions[clade] = ((positions[clade.clades[0]] + |
| 43 | + positions[clade.clades[-1]]) // 2) |
| 44 | + |
| 45 | + calc_row(tree.root) |
| 46 | + return positions |
| 47 | + |
| 48 | + def add_to_elements(clade, clade_id): |
18 | 49 | children = clade.clades |
19 | 50 |
|
20 | | - cy_source = {"data": {"id": clade_id}, 'classes': 'nonterminal'} |
21 | | - elements.append(cy_source) |
| 51 | + pos_x = col_positions[clade] * xlen |
| 52 | + pos_y = row_positions[clade] * ylen |
| 53 | + |
| 54 | + cy_source = { |
| 55 | + "data": {"id": clade_id}, |
| 56 | + 'position': {'x': pos_x, 'y': pos_y}, |
| 57 | + 'classes': 'nonterminal', |
| 58 | + 'grabbable': grabbable |
| 59 | + } |
| 60 | + nodes.append(cy_source) |
22 | 61 |
|
23 | 62 | if clade.is_terminal(): |
24 | 63 | cy_source['data']['name'] = clade.name |
25 | 64 | cy_source['classes'] = 'terminal' |
26 | 65 |
|
27 | | - for n, child in enumerate(children, 1): |
28 | | - child_id = len(elements) + n |
29 | | - |
30 | | - cy_edge = {'data': { |
31 | | - 'source': clade_id, |
32 | | - 'target': child_id, |
33 | | - 'length': clade.branch_length |
34 | | - }} |
| 66 | + for n, child in enumerate(children): |
| 67 | + # The "support" node is on the same column as the parent clade, |
| 68 | + # and on the same row as the child clade. It is used to create the |
| 69 | + # 90 degree angle between the parent and the children. |
| 70 | + # Edge config: parent -> support -> child |
| 71 | + |
| 72 | + support_id = clade_id + 's' + str(n) |
| 73 | + child_id = clade_id + 'c' + str(n) |
| 74 | + pos_y_child = row_positions[child] * ylen |
| 75 | + |
| 76 | + cy_support_node = { |
| 77 | + 'data': {'id': support_id}, |
| 78 | + 'position': {'x': pos_x, 'y': pos_y_child}, |
| 79 | + 'grabbable': grabbable, |
| 80 | + 'classes': 'support' |
| 81 | + } |
| 82 | + |
| 83 | + cy_support_edge = { |
| 84 | + 'data': { |
| 85 | + 'source': clade_id, |
| 86 | + 'target': support_id, |
| 87 | + 'sourceCladeId': clade_id |
| 88 | + }, |
| 89 | + } |
| 90 | + |
| 91 | + cy_edge = { |
| 92 | + 'data': { |
| 93 | + 'source': support_id, |
| 94 | + 'target': child_id, |
| 95 | + 'length': clade.branch_length, |
| 96 | + 'sourceCladeId': clade_id |
| 97 | + }, |
| 98 | + } |
35 | 99 |
|
36 | 100 | if clade.confidence and clade.confidence.value: |
37 | 101 | cy_source['data']['confidence'] = clade.confidence.value |
38 | 102 |
|
39 | | - elements.extend([cy_edge]) |
| 103 | + nodes.append(cy_support_node) |
| 104 | + edges.extend([cy_support_edge, cy_edge]) |
| 105 | + |
| 106 | + add_to_elements(child, child_id) |
| 107 | + |
| 108 | + col_positions = get_col_positions(tree) |
| 109 | + row_positions = get_row_positions(tree) |
40 | 110 |
|
41 | | - _add_to_elements(child, child_id) |
| 111 | + nodes = [] |
| 112 | + edges = [] |
42 | 113 |
|
43 | | - _add_to_elements(tree.clade, 0) |
| 114 | + add_to_elements(tree.clade, 'r') |
44 | 115 |
|
45 | | - return elements |
| 116 | + return nodes, edges |
46 | 117 |
|
47 | 118 |
|
48 | 119 | # Define elements, stylesheet and layout |
49 | 120 | tree = Phylo.read('data/apaf.xml', 'phyloxml') |
50 | | -elements = generate_elements(tree) |
| 121 | +nodes, edges = generate_elements(tree) |
| 122 | +elements = nodes + edges |
51 | 123 |
|
52 | 124 | layout = { |
53 | 125 | 'name': 'breadthfirst', |
|
0 commit comments