|
24 | 24 | Module responsible for visualisations. |
25 | 25 | """ |
26 | 26 | import collections |
27 | | -from _tskit import NULL |
| 27 | +import numbers |
28 | 28 |
|
29 | 29 | import svgwrite |
30 | 30 | import numpy as np |
31 | 31 |
|
| 32 | +from _tskit import NULL |
| 33 | + |
| 34 | +LEFT = "left" |
| 35 | +RIGHT = "right" |
| 36 | +TOP = "top" |
| 37 | +BOTTOM = "bottom" |
| 38 | + |
| 39 | + |
| 40 | +def check_orientation(orientation): |
| 41 | + if orientation is None: |
| 42 | + orientation = TOP |
| 43 | + else: |
| 44 | + orientation = orientation.lower() |
| 45 | + orientations = [LEFT, RIGHT, TOP, BOTTOM] |
| 46 | + if orientation not in orientations: |
| 47 | + raise ValueError( |
| 48 | + "Unknown orientiation: choose from {}".format(orientations)) |
| 49 | + return orientation |
| 50 | + |
| 51 | + |
| 52 | +def check_max_tree_height(max_tree_height, allow_numeric=True): |
| 53 | + if max_tree_height is None: |
| 54 | + max_tree_height = "tree" |
| 55 | + is_numeric = isinstance(max_tree_height, numbers.Real) |
| 56 | + if max_tree_height not in ["tree", "ts"] and not allow_numeric: |
| 57 | + raise ValueError("max_tree_height must be 'tree' or 'ts'") |
| 58 | + if max_tree_height not in ["tree", "ts"] and (allow_numeric and not is_numeric): |
| 59 | + raise ValueError( |
| 60 | + "max_tree_height must be a numeric value or one of 'tree' or 'ts'") |
| 61 | + return max_tree_height |
| 62 | + |
| 63 | + |
| 64 | +def check_tree_height_scale(tree_height_scale): |
| 65 | + if tree_height_scale is None: |
| 66 | + tree_height_scale = "time" |
| 67 | + if tree_height_scale not in ["time", "log_time", "rank"]: |
| 68 | + raise ValueError("tree_height_scale must be 'time', 'log_time' or 'rank'") |
| 69 | + return tree_height_scale |
| 70 | + |
32 | 71 |
|
33 | 72 | def check_format(format): |
34 | 73 | if format is None: |
@@ -264,28 +303,39 @@ def setup_drawing(self): |
264 | 303 | self.mutation_right_labels = self.mutation_labels.add(dwg.g(text_anchor="end")) |
265 | 304 |
|
266 | 305 | def assign_y_coordinates(self, tree_height_scale, max_tree_height): |
| 306 | + tree_height_scale = check_tree_height_scale(tree_height_scale) |
| 307 | + max_tree_height = check_max_tree_height( |
| 308 | + max_tree_height, tree_height_scale != "rank") |
267 | 309 | ts = self.tree.tree_sequence |
268 | 310 | node_time = ts.tables.nodes.time |
269 | | - if tree_height_scale in [None, "time", "log_time"]: |
270 | | - if max_tree_height in [None, "tree"]: |
| 311 | + |
| 312 | + if tree_height_scale == "rank": |
| 313 | + assert tree_height_scale == "rank" |
| 314 | + if max_tree_height == "tree": |
| 315 | + # We only rank the times within the tree in this case. |
| 316 | + t = np.zeros_like(node_time) + node_time[self.tree.left_root] |
| 317 | + for u in self.tree.nodes(): |
| 318 | + t[u] = node_time[u] |
| 319 | + node_time = t |
| 320 | + depth = {t: 2 * j for j, t in enumerate(np.unique(node_time))} |
| 321 | + node_height = [depth[node_time[u]] for u in range(ts.num_nodes)] |
| 322 | + max_tree_height = max(depth.values()) |
| 323 | + else: |
| 324 | + assert tree_height_scale in ["time", "log_time"] |
| 325 | + if max_tree_height == "tree": |
271 | 326 | max_tree_height = max(self.tree.time(root) for root in self.tree.roots) |
272 | 327 | elif max_tree_height == "ts": |
273 | 328 | max_tree_height = ts.max_root_time |
274 | | - if tree_height_scale == "log_time": |
275 | | - # add 1 so that don't reach log(0) = -inf error. |
276 | | - # just shifts entire timeset by 1 year so shouldn't affect anything |
277 | | - node_height = np.log(ts.tables.nodes.time + 1) |
278 | | - elif tree_height_scale in [None, "time"]: |
279 | | - node_height = node_time |
280 | | - else: |
281 | | - if tree_height_scale != "rank": |
282 | | - raise ValueError( |
283 | | - "Only 'time', 'log_time', " |
284 | | - "and 'rank' are supported for tree_height_scale") |
285 | | - depth = {t: 2 * j for j, t in enumerate(np.unique(node_time))} |
286 | | - node_height = [depth[node_time[u]] for u in range(ts.num_nodes)] |
287 | | - if max_tree_height is None: |
288 | | - max_tree_height = max(depth.values()) |
| 329 | + |
| 330 | + if tree_height_scale == "log_time": |
| 331 | + # add 1 so that don't reach log(0) = -inf error. |
| 332 | + # just shifts entire timeset by 1 year so shouldn't affect anything |
| 333 | + node_height = np.log(ts.tables.nodes.time + 1) |
| 334 | + elif tree_height_scale == "time": |
| 335 | + node_height = node_time |
| 336 | + |
| 337 | + assert float(max_tree_height) == max_tree_height |
| 338 | + |
289 | 339 | # In pathological cases, all the roots are at 0 |
290 | 340 | if max_tree_height == 0: |
291 | 341 | max_tree_height = 1 |
@@ -466,32 +516,6 @@ def __str__(self): |
466 | 516 | return "".join(self.canvas.reshape(self.width * self.height)) |
467 | 517 |
|
468 | 518 |
|
469 | | -LEFT = "left" |
470 | | -RIGHT = "right" |
471 | | -TOP = "top" |
472 | | -BOTTOM = "bottom" |
473 | | - |
474 | | - |
475 | | -def check_orientation(orientation): |
476 | | - if orientation is None: |
477 | | - orientation = TOP |
478 | | - else: |
479 | | - orientation = orientation.lower() |
480 | | - orientations = [LEFT, RIGHT, TOP, BOTTOM] |
481 | | - if orientation not in orientations: |
482 | | - raise ValueError( |
483 | | - "Unknown orientiation: choose from {}".format(orientations)) |
484 | | - return orientation |
485 | | - |
486 | | - |
487 | | -def check_max_tree_height(max_tree_height): |
488 | | - if max_tree_height is None: |
489 | | - max_tree_height = "tree" |
490 | | - if max_tree_height not in ["tree", "ts"]: |
491 | | - raise ValueError("max_tree_height must be 'tree' or 'ts'") |
492 | | - return max_tree_height |
493 | | - |
494 | | - |
495 | 519 | def to_np_unicode(string): |
496 | 520 | """ |
497 | 521 | Converts the specified string to a numpy unicode array. |
@@ -574,7 +598,8 @@ def __init__( |
574 | 598 | self, tree, node_labels=None, max_tree_height=None, use_ascii=False, |
575 | 599 | orientation=None): |
576 | 600 | self.tree = tree |
577 | | - self.max_tree_height = check_max_tree_height(max_tree_height) |
| 601 | + self.max_tree_height = check_max_tree_height( |
| 602 | + max_tree_height, allow_numeric=False) |
578 | 603 | self.use_ascii = use_ascii |
579 | 604 | self.orientation = check_orientation(orientation) |
580 | 605 | self.horizontal_line_char = '━' |
|
0 commit comments