iplotx 1.3.0__py3-none-any.whl → 1.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iplotx/edge/__init__.py +8 -1
- iplotx/edge/geometry.py +113 -0
- iplotx/layout/tree/unrooted.py +118 -57
- iplotx/style/leaf_info.py +1 -0
- iplotx/version.py +1 -1
- {iplotx-1.3.0.dist-info → iplotx-1.4.1.dist-info}/METADATA +1 -1
- {iplotx-1.3.0.dist-info → iplotx-1.4.1.dist-info}/RECORD +8 -8
- {iplotx-1.3.0.dist-info → iplotx-1.4.1.dist-info}/WHEEL +0 -0
iplotx/edge/__init__.py
CHANGED
|
@@ -370,6 +370,9 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
370
370
|
if edge_stylei.get("curved", False):
|
|
371
371
|
tension = edge_stylei.get("tension", 5)
|
|
372
372
|
ports = edge_stylei.get("ports", (None, None))
|
|
373
|
+
elif edge_stylei.get("arc", False):
|
|
374
|
+
tension = edge_stylei.get("tension", 1)
|
|
375
|
+
ports = None
|
|
373
376
|
else:
|
|
374
377
|
tension = 0
|
|
375
378
|
ports = None
|
|
@@ -391,6 +394,8 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
391
394
|
if waypoints != "none":
|
|
392
395
|
ports = edge_stylei.get("ports", (None, None))
|
|
393
396
|
|
|
397
|
+
arc = edge_stylei.get("arc", False)
|
|
398
|
+
|
|
394
399
|
# Compute actual edge path
|
|
395
400
|
path, angles = _compute_edge_path(
|
|
396
401
|
vcoord_data,
|
|
@@ -401,6 +406,7 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
401
406
|
tension=tension,
|
|
402
407
|
waypoints=waypoints,
|
|
403
408
|
ports=ports,
|
|
409
|
+
arc=arc,
|
|
404
410
|
layout_coordinate_system=self._vertex_collection.get_layout_coordinate_system(),
|
|
405
411
|
shrink=shrink,
|
|
406
412
|
)
|
|
@@ -447,7 +453,7 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
447
453
|
# If none found, empty the dictionary already
|
|
448
454
|
if (len(parallel_edges) == 0) or (max(parallel_edges.values(), key=len) == 1):
|
|
449
455
|
parallel_edges = {}
|
|
450
|
-
if not self._style.get("curved", False):
|
|
456
|
+
if (not self._style.get("curved", False)) and (not self._style.get("arc", False)):
|
|
451
457
|
while len(parallel_edges) > 0:
|
|
452
458
|
(v1, v2), indices = parallel_edges.popitem()
|
|
453
459
|
indices_inv = parallel_edges.pop((v2, v1), [])
|
|
@@ -712,6 +718,7 @@ def make_stub_patch(**kwargs):
|
|
|
712
718
|
"split",
|
|
713
719
|
"shrink",
|
|
714
720
|
"depthshade",
|
|
721
|
+
"arc",
|
|
715
722
|
# DEPRECATED
|
|
716
723
|
"padding",
|
|
717
724
|
]
|
iplotx/edge/geometry.py
CHANGED
|
@@ -393,6 +393,108 @@ def _compute_edge_path_waypoints(
|
|
|
393
393
|
return path, angles
|
|
394
394
|
|
|
395
395
|
|
|
396
|
+
def _compute_edge_path_arc(
|
|
397
|
+
tension,
|
|
398
|
+
vcoord_data,
|
|
399
|
+
vpath_fig,
|
|
400
|
+
vsize_fig,
|
|
401
|
+
trans,
|
|
402
|
+
trans_inv,
|
|
403
|
+
ports: Pair[Optional[str]] = (None, None),
|
|
404
|
+
shrink: float = 0,
|
|
405
|
+
):
|
|
406
|
+
"""Shorten the edge path along an arc.
|
|
407
|
+
|
|
408
|
+
Parameters:
|
|
409
|
+
tension: the tension of the arc. This is defined, for this function, as the tangent
|
|
410
|
+
of the angle spanning the arc. For instance, for a semicircle, the angle is
|
|
411
|
+
180 degrees, so the tension is +-1 (depending on the orientation).
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
# Coordinates in figure (default) coords
|
|
415
|
+
vcoord_fig = trans(vcoord_data)
|
|
416
|
+
|
|
417
|
+
dv = vcoord_fig[1] - vcoord_fig[0]
|
|
418
|
+
|
|
419
|
+
# Tension is the fraction of the semicircle covered by the
|
|
420
|
+
# arc. Values are clipped between -1 (left-hand semicircle)
|
|
421
|
+
# and 1 (right-hand semicircle). 0 means a straight line,
|
|
422
|
+
# which is a (degenerate) arc too.
|
|
423
|
+
if tension == 0:
|
|
424
|
+
vs = [None, None]
|
|
425
|
+
thetas = [atan2(dv[1], dv[0])]
|
|
426
|
+
thetas.append(-thetas[0])
|
|
427
|
+
for i in range(2):
|
|
428
|
+
vs[i] = (
|
|
429
|
+
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i], shrink)
|
|
430
|
+
+ vcoord_fig[i]
|
|
431
|
+
)
|
|
432
|
+
auxs = []
|
|
433
|
+
|
|
434
|
+
else:
|
|
435
|
+
edge_straight_length = np.sqrt((dv**2).sum())
|
|
436
|
+
theta_straight = atan2(dv[1], dv[0])
|
|
437
|
+
theta_tension = 4 * np.arctan(tension)
|
|
438
|
+
# print(f"theta_straight: {np.degrees(theta_straight):.2f}")
|
|
439
|
+
# print(f"theta_tension: {np.degrees(theta_tension):.2f}")
|
|
440
|
+
# NOTE: positive tension means an arc shooting off to the right of the straight
|
|
441
|
+
# line, same convensio as for tension elsewhere in the codebase.
|
|
442
|
+
thetas = [theta_straight - theta_tension / 2, np.pi + theta_straight + theta_tension / 2]
|
|
443
|
+
# This is guaranteed to be finite because tension == 0 is taken care of above,
|
|
444
|
+
# and tension = np.inf is not allowed.
|
|
445
|
+
mid = vcoord_fig.mean(axis=0)
|
|
446
|
+
# print(f"theta_s: {thetas}")
|
|
447
|
+
# print(f"mid: {mid}")
|
|
448
|
+
theta_offset = theta_straight + np.pi / 2
|
|
449
|
+
if np.abs(tension) <= 1:
|
|
450
|
+
offset_length = edge_straight_length / 2 / np.tan(theta_tension / 2)
|
|
451
|
+
else:
|
|
452
|
+
# print("Large tension arc")
|
|
453
|
+
offset_length = -edge_straight_length / 2 * np.tan(theta_tension / 2 - np.pi / 2)
|
|
454
|
+
# print(f"theta_offset: {np.degrees(theta_offset):.2f}")
|
|
455
|
+
offset = offset_length * np.array([np.cos(theta_offset), np.sin(theta_offset)])
|
|
456
|
+
# print(f"offset: {offset}")
|
|
457
|
+
center = mid + offset
|
|
458
|
+
# print(f"center: {center}")
|
|
459
|
+
|
|
460
|
+
# Compute shorter start and end points
|
|
461
|
+
vs = [None, None]
|
|
462
|
+
for i in range(2):
|
|
463
|
+
vs[i] = (
|
|
464
|
+
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i], shrink)
|
|
465
|
+
+ vcoord_fig[i]
|
|
466
|
+
)
|
|
467
|
+
angle_start = atan2(*(vs[0] - center)[::-1])
|
|
468
|
+
angle_end = atan2(*(vs[1] - center)[::-1])
|
|
469
|
+
if (np.abs(tension) > 1) and (np.abs(angle_end - angle_start) < np.pi):
|
|
470
|
+
if angle_end > angle_start:
|
|
471
|
+
angle_start += 2 * np.pi
|
|
472
|
+
else:
|
|
473
|
+
angle_end += 2 * np.pi
|
|
474
|
+
# print(f"angle_start: {np.degrees(angle_start):.2f}")
|
|
475
|
+
# print(f"angle_end: {np.degrees(angle_end):.2f}")
|
|
476
|
+
|
|
477
|
+
naux = 30
|
|
478
|
+
angles = np.linspace(angle_start, angle_end, naux + 2)[1:-1]
|
|
479
|
+
auxs = center + np.array([np.cos(angles), np.sin(angles)]).T * np.linalg.norm(
|
|
480
|
+
vs[0] - center
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
path = {
|
|
484
|
+
"vertices": [vs[0]] + list(auxs) + [vs[1]],
|
|
485
|
+
"codes": ["MOVETO"] + ["LINETO"] * (len(auxs) + 1),
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
path = mpl.path.Path(
|
|
489
|
+
path["vertices"],
|
|
490
|
+
codes=[getattr(mpl.path.Path, x) for x in path["codes"]],
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
# Return to data transform
|
|
494
|
+
path.vertices = trans_inv(path.vertices)
|
|
495
|
+
return path, tuple(thetas)
|
|
496
|
+
|
|
497
|
+
|
|
396
498
|
def _compute_edge_path_curved(
|
|
397
499
|
tension,
|
|
398
500
|
vcoord_data,
|
|
@@ -483,12 +585,15 @@ def _compute_edge_path(
|
|
|
483
585
|
tension: float = 0,
|
|
484
586
|
waypoints: str | tuple[float, float] | Sequence[tuple[float, float]] | np.ndarray = "none",
|
|
485
587
|
ports: Pair[Optional[str]] = (None, None),
|
|
588
|
+
arc: bool = False,
|
|
486
589
|
layout_coordinate_system: str = "cartesian",
|
|
487
590
|
**kwargs,
|
|
488
591
|
):
|
|
489
592
|
"""Compute the edge path in a few different ways."""
|
|
490
593
|
if (waypoints != "none") and (tension != 0):
|
|
491
594
|
raise ValueError("Waypoints not supported for curved edges.")
|
|
595
|
+
if (waypoints != "none") and arc:
|
|
596
|
+
raise ValueError("Waypoint not supported for arc edges.")
|
|
492
597
|
|
|
493
598
|
if waypoints != "none":
|
|
494
599
|
return _compute_edge_path_waypoints(
|
|
@@ -506,6 +611,14 @@ def _compute_edge_path(
|
|
|
506
611
|
**kwargs,
|
|
507
612
|
)
|
|
508
613
|
|
|
614
|
+
if arc:
|
|
615
|
+
return _compute_edge_path_arc(
|
|
616
|
+
tension,
|
|
617
|
+
*args,
|
|
618
|
+
ports=ports,
|
|
619
|
+
**kwargs,
|
|
620
|
+
)
|
|
621
|
+
|
|
509
622
|
return _compute_edge_path_curved(
|
|
510
623
|
tension,
|
|
511
624
|
*args,
|
iplotx/layout/tree/unrooted.py
CHANGED
|
@@ -74,13 +74,15 @@ def _equalangle_tree_layout(
|
|
|
74
74
|
for child in children:
|
|
75
75
|
nleaves_child = props["nleaves"][child]
|
|
76
76
|
alpha = nleaves_child / nleaves * total_angle
|
|
77
|
+
if orientation in ("left", "counterclockwise"):
|
|
78
|
+
alpha = -alpha
|
|
77
79
|
beta = start + alpha / 2
|
|
78
80
|
|
|
79
81
|
props["layout"][child] = [
|
|
80
82
|
cur_x + branch_length_fun(child) * np.cos(np.radians(beta)),
|
|
81
83
|
cur_y + branch_length_fun(child) * np.sin(np.radians(beta)),
|
|
82
84
|
]
|
|
83
|
-
props["angle"][child] = -90 - beta * np.sign(beta - 180)
|
|
85
|
+
# props["angle"][child] = -90 - beta * np.sign(beta - 180)
|
|
84
86
|
props["start"][child] = start
|
|
85
87
|
props["end"][child] = start + alpha
|
|
86
88
|
start += alpha
|
|
@@ -101,7 +103,8 @@ def _daylight_tree_layout(
|
|
|
101
103
|
start: float = 180,
|
|
102
104
|
span: float = 360,
|
|
103
105
|
max_iter: int = 5,
|
|
104
|
-
dampening: float = 0.
|
|
106
|
+
dampening: float = 0.33,
|
|
107
|
+
max_correction: float = 10.0,
|
|
105
108
|
**kwargs,
|
|
106
109
|
) -> dict[Hashable, list[float]]:
|
|
107
110
|
"""Daylight unrooted tree layout.
|
|
@@ -154,16 +157,34 @@ def _daylight_tree_layout(
|
|
|
154
157
|
ninternal = 0
|
|
155
158
|
parents = [None] + list(levelorder_fun())
|
|
156
159
|
for parent in parents:
|
|
157
|
-
|
|
160
|
+
if parent is None:
|
|
161
|
+
# If the root has only two children, it's a passthrough node, skip it
|
|
162
|
+
if len(children_fun(root)) < 3:
|
|
163
|
+
continue
|
|
164
|
+
# Else, include it
|
|
165
|
+
children = [root]
|
|
166
|
+
else:
|
|
167
|
+
children = children_fun(parent)
|
|
168
|
+
|
|
158
169
|
for node in children:
|
|
170
|
+
grandchildren = children_fun(node)
|
|
171
|
+
# Exclude leaves, since they have no children subtrees
|
|
172
|
+
# that can be adjusted. Exclude also passthrough nodes with
|
|
173
|
+
# a single child, because they are rotating rigidly when their
|
|
174
|
+
# parent does so or tells them to do so.
|
|
175
|
+
if len(grandchildren) < 2:
|
|
176
|
+
continue
|
|
177
|
+
|
|
159
178
|
res = _apply_daylight_single_node(
|
|
160
179
|
node,
|
|
161
180
|
parent,
|
|
181
|
+
grandchildren,
|
|
162
182
|
all_leaves,
|
|
163
183
|
layout,
|
|
164
184
|
leaves_fun,
|
|
165
185
|
children_fun,
|
|
166
186
|
dampening,
|
|
187
|
+
max_correction,
|
|
167
188
|
)
|
|
168
189
|
change_sum += res
|
|
169
190
|
ninternal += 1
|
|
@@ -182,11 +203,13 @@ def _daylight_tree_layout(
|
|
|
182
203
|
def _apply_daylight_single_node(
|
|
183
204
|
node: Any,
|
|
184
205
|
parent: Any,
|
|
206
|
+
children: list[Any],
|
|
185
207
|
all_leaves: list[Any],
|
|
186
208
|
layout: dict[Hashable, np.ndarray],
|
|
187
209
|
leaves_fun: Callable,
|
|
188
210
|
children_fun: Callable,
|
|
189
211
|
dampening: float,
|
|
212
|
+
max_correction: float,
|
|
190
213
|
) -> float:
|
|
191
214
|
"""Apply daylight adjustment to a single internal node.
|
|
192
215
|
|
|
@@ -208,9 +231,9 @@ def _apply_daylight_single_node(
|
|
|
208
231
|
|
|
209
232
|
print = _print if DEBUG_DAYLIGHT else lambda *a, **k: None
|
|
210
233
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
#
|
|
234
|
+
# 1. Find daylight boundary leaves for each subtree. There are always at least two subtrees,
|
|
235
|
+
# the first child and the parent (this function is not called for leaves, and the root hopefully
|
|
236
|
+
# has at least two children).
|
|
214
237
|
p0 = layout[node]
|
|
215
238
|
bounds = {}
|
|
216
239
|
|
|
@@ -224,99 +247,137 @@ def _apply_daylight_single_node(
|
|
|
224
247
|
|
|
225
248
|
# Check the parent first if there is one
|
|
226
249
|
if parent is not None:
|
|
250
|
+
leaves_parent_subtree = [leaf for leaf in all_leaves if leaf not in leaves_below]
|
|
227
251
|
vec1 = layout[parent] - p0
|
|
228
252
|
print("parent side leaves:")
|
|
229
253
|
print(parent)
|
|
230
|
-
print(
|
|
254
|
+
print(
|
|
255
|
+
f" node to parent vector: {vec1[0]:.2f}, {vec1[1]:.2f}, angle: {np.degrees(np.arctan2(vec1[1], vec1[0])):.2f}"
|
|
256
|
+
)
|
|
231
257
|
lower_angle, upper_angle = 2 * np.pi, -2 * np.pi
|
|
232
|
-
for leaf in
|
|
233
|
-
# Skip subtree leaves
|
|
234
|
-
if leaf in leaves_below:
|
|
235
|
-
continue
|
|
258
|
+
for leaf in leaves_parent_subtree:
|
|
236
259
|
vec2 = layout[leaf] - p0
|
|
237
260
|
angle = _anticlockwise_angle(vec1, vec2)
|
|
238
261
|
print(" parent side leaf:")
|
|
239
262
|
print(leaf)
|
|
240
|
-
print(
|
|
241
|
-
|
|
263
|
+
print(
|
|
264
|
+
f" node to leaf vector: {vec2[0]:.2f}, {vec2[1]:.2f}, angle: {np.degrees(np.arctan2(vec2[1], vec2[0])):.2f}"
|
|
265
|
+
)
|
|
266
|
+
print(f" angle: {np.degrees(angle):.2f}")
|
|
242
267
|
if angle < lower_angle:
|
|
268
|
+
print("lowering lower angle")
|
|
243
269
|
lower_angle = angle
|
|
244
270
|
lower = leaf
|
|
271
|
+
else:
|
|
272
|
+
print("not lowering lower angle")
|
|
245
273
|
if angle > upper_angle:
|
|
274
|
+
print("raising upper angle")
|
|
246
275
|
upper_angle = angle
|
|
247
276
|
upper = leaf
|
|
277
|
+
else:
|
|
278
|
+
print("not raising upper angle")
|
|
248
279
|
bounds[parent] = (lower, upper, lower_angle, upper_angle)
|
|
249
280
|
|
|
250
281
|
# Repeat the exact same thing for each child rather than the parent
|
|
251
282
|
print("subtree leaves:")
|
|
252
283
|
for child in children:
|
|
253
284
|
vec1 = layout[child] - p0
|
|
254
|
-
print(
|
|
285
|
+
print(
|
|
286
|
+
f" node to child vector: {vec1[0]:.2f}, {vec1[1]:.2f}, angle: {np.degrees(np.arctan2(vec1[1], vec1[0])):.2f}"
|
|
287
|
+
)
|
|
255
288
|
lower_angle, upper_angle = 2 * np.pi, -2 * np.pi
|
|
256
289
|
|
|
257
290
|
for leaf in leaves_fun(child):
|
|
258
291
|
vec2 = layout[leaf] - p0
|
|
259
292
|
angle = _anticlockwise_angle(vec1, vec2)
|
|
260
|
-
print(
|
|
293
|
+
print(
|
|
294
|
+
f" node to leaf vector: {vec2[0]:.2f}, {vec2[1]:.2f}, angle: {np.degrees(np.arctan2(vec2[1], vec2[0])):.2f}"
|
|
295
|
+
)
|
|
261
296
|
print(leaf)
|
|
262
|
-
print(f" angle: {angle:.2f}")
|
|
297
|
+
print(f" angle: {np.degrees(angle):.2f}")
|
|
263
298
|
if angle < lower_angle:
|
|
299
|
+
print("lowering lower angle")
|
|
264
300
|
lower_angle = angle
|
|
265
301
|
lower = leaf
|
|
302
|
+
else:
|
|
303
|
+
print("not lowering lower angle")
|
|
266
304
|
if angle > upper_angle:
|
|
305
|
+
print("raising upper angle")
|
|
267
306
|
upper_angle = angle
|
|
268
307
|
upper = leaf
|
|
308
|
+
else:
|
|
309
|
+
print("not raising upper angle")
|
|
269
310
|
bounds[child] = (lower, upper, lower_angle, upper_angle)
|
|
270
311
|
|
|
271
|
-
|
|
272
|
-
|
|
312
|
+
for subtree, bound in bounds.items():
|
|
313
|
+
vec1 = layout[bound[0]] - p0
|
|
314
|
+
vec2 = layout[bound[1]] - p0
|
|
315
|
+
angle = _anticlockwise_angle(vec1, vec2)
|
|
316
|
+
print("subtree angles:")
|
|
317
|
+
print(f" lower {np.degrees(np.arctan2(vec1[1], vec1[0])):.2f}")
|
|
318
|
+
print(f" upper {np.degrees(np.arctan2(vec2[1], vec2[0])):.2f}")
|
|
319
|
+
print(f" angle {np.degrees(angle):.2f}")
|
|
273
320
|
|
|
274
321
|
# 2. Compute daylight angles
|
|
275
322
|
# NOTE: Since Python 3.6, python keys are ordered by insertion order.
|
|
276
323
|
daylight = {}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
324
|
+
subtrees = list(bounds.keys())
|
|
325
|
+
subtrees += [subtrees[0]] # Repeat first subtree
|
|
326
|
+
|
|
327
|
+
for i in range(len(subtrees) - 1):
|
|
328
|
+
subtree = subtrees[i + 1]
|
|
329
|
+
old_subtree = subtrees[i]
|
|
330
|
+
lower = bounds[subtree][0]
|
|
331
|
+
old_upper = bounds[old_subtree][1]
|
|
332
|
+
vec1 = layout[old_upper] - p0
|
|
333
|
+
vec2 = layout[lower] - p0
|
|
286
334
|
angle = _anticlockwise_angle(vec1, vec2)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
print(daylight)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
335
|
+
daylight[subtree] = float(angle)
|
|
336
|
+
print("daylight angle:")
|
|
337
|
+
print(f" previous upper {np.degrees(np.arctan2(vec1[1], vec1[0])):.2f}")
|
|
338
|
+
print(f" new lower {np.degrees(np.arctan2(vec2[1], vec2[0])):.2f}")
|
|
339
|
+
print(f" angle: {np.degrees(angle):.2f}")
|
|
340
|
+
|
|
341
|
+
daylight_avg = sum(daylight.values()) / len(daylight)
|
|
342
|
+
print(f"daylight average angle: {np.degrees(daylight_avg):.2f}")
|
|
343
|
+
|
|
344
|
+
# 3. Compute *excess* daylight and corrections
|
|
345
|
+
daylight_correction = {}
|
|
346
|
+
corr_cum = 0.0
|
|
347
|
+
print("daylight correction:")
|
|
348
|
+
for subtree, angle in daylight.items():
|
|
349
|
+
# Correction is negative of the residue
|
|
350
|
+
corr_cum -= angle - daylight_avg
|
|
351
|
+
daylight_correction[subtree] = corr_cum
|
|
352
|
+
print(f" daylight angle: {np.degrees(angle):.2f}, correction: {np.degrees(corr_cum):.2f}")
|
|
353
|
+
|
|
354
|
+
# NOTE: the last daylight correction must be 0, otherwise we are just rotating the entire tree.
|
|
355
|
+
# In most cases, this will be the parent which cannot rotate anyway (for the same reason).
|
|
356
|
+
# However, when applied to the root node with 3+ children, the nonrotating one will be the first
|
|
357
|
+
# child, which is arbitrary but correct: even in this case, we do not want a merry-go-round.
|
|
358
|
+
|
|
359
|
+
# 4. Correct (the last one is dumb)
|
|
360
|
+
daylight_corrections_abs = 0.0
|
|
361
|
+
for subtree, correction in daylight_correction.items():
|
|
362
|
+
correction *= dampening
|
|
363
|
+
correction = np.clip(correction, np.radians(-max_correction), np.radians(max_correction))
|
|
364
|
+
print(f"Applying correction to subtree {subtree}: {np.degrees(correction):.2f}")
|
|
365
|
+
_rotate_subtree_anticlockwise(
|
|
309
366
|
leaf,
|
|
310
367
|
children_fun,
|
|
311
368
|
layout,
|
|
312
369
|
p0,
|
|
313
|
-
|
|
370
|
+
correction,
|
|
314
371
|
recur=True,
|
|
315
372
|
)
|
|
316
|
-
|
|
373
|
+
daylight_corrections_abs += abs(correction)
|
|
374
|
+
|
|
375
|
+
# __import__("ipdb").set_trace()
|
|
317
376
|
|
|
318
377
|
# Caller wants degrees
|
|
319
|
-
|
|
378
|
+
# NOTE: The denominator is -1 because the last correction is always zero anyway, so the
|
|
379
|
+
# actually possible corrections are #subtrees - 1.
|
|
380
|
+
return np.degrees(daylight_corrections_abs / (len(daylight_correction) - 1))
|
|
320
381
|
|
|
321
382
|
|
|
322
383
|
# see: https://stackoverflow.com/questions/14066933/direct-way-of-computing-the-clockwise-angle-between-two-vectors
|
|
@@ -334,7 +395,7 @@ def _anticlockwise_angle(v1, v2):
|
|
|
334
395
|
return np.arctan2(determinant, dot)
|
|
335
396
|
|
|
336
397
|
|
|
337
|
-
def
|
|
398
|
+
def _rotate_subtree_anticlockwise(
|
|
338
399
|
node,
|
|
339
400
|
children_fun: Callable,
|
|
340
401
|
layout: dict[Hashable, list[float]],
|
|
@@ -344,7 +405,7 @@ def _rotate_subtree_around_point(
|
|
|
344
405
|
):
|
|
345
406
|
point = np.asarray(layout[node])
|
|
346
407
|
pivot = np.asarray(pivot)
|
|
347
|
-
layout[node] =
|
|
408
|
+
layout[node] = _rotate_anticlockwise(
|
|
348
409
|
point,
|
|
349
410
|
pivot,
|
|
350
411
|
angle,
|
|
@@ -352,7 +413,7 @@ def _rotate_subtree_around_point(
|
|
|
352
413
|
if not recur:
|
|
353
414
|
return
|
|
354
415
|
for child in children_fun(node):
|
|
355
|
-
|
|
416
|
+
_rotate_subtree_anticlockwise(
|
|
356
417
|
child,
|
|
357
418
|
children_fun,
|
|
358
419
|
layout,
|
|
@@ -361,7 +422,7 @@ def _rotate_subtree_around_point(
|
|
|
361
422
|
)
|
|
362
423
|
|
|
363
424
|
|
|
364
|
-
def
|
|
425
|
+
def _rotate_anticlockwise(
|
|
365
426
|
point,
|
|
366
427
|
pivot,
|
|
367
428
|
angle,
|
|
@@ -379,5 +440,5 @@ def _rotate_around_point(
|
|
|
379
440
|
pivot = np.asarray(pivot)
|
|
380
441
|
cos = np.cos(angle)
|
|
381
442
|
sin = np.sin(angle)
|
|
382
|
-
rot = np.array([[cos, sin], [
|
|
443
|
+
rot = np.array([[cos, -sin], [sin, cos]])
|
|
383
444
|
return pivot + (point - pivot) @ rot
|
iplotx/style/leaf_info.py
CHANGED
iplotx/version.py
CHANGED
|
@@ -3,15 +3,15 @@ iplotx/artists.py,sha256=2dBDT240zGwKb6tIc_y9pXeyU3LuYeF9wjj2tvi4KJo,730
|
|
|
3
3
|
iplotx/label.py,sha256=7eS8ByadrhdIFOZz19U4VrS-oXY_ndFYNB-D4RZbFqI,9573
|
|
4
4
|
iplotx/plotting.py,sha256=RyAdvaHSpuyJkf8DF3SJBvEXBrPmJEdovUyAlBWQvqU,16228
|
|
5
5
|
iplotx/typing.py,sha256=QLdzV358IiD1CFe88MVp0D77FSx5sSAVUmM_2WPPE8I,1463
|
|
6
|
-
iplotx/version.py,sha256=
|
|
6
|
+
iplotx/version.py,sha256=pDHOlKcY4NkjVyT63s4sF7EXMk_RlMLYxzkdejRQNr8,66
|
|
7
7
|
iplotx/vertex.py,sha256=_yYyvusn4vYvi6RBEW6CHa3vnbv43GnZylnMIaK4bG0,16040
|
|
8
8
|
iplotx/art3d/vertex.py,sha256=Xf8Um30X2doCd8KdNN7332F6BxC4k72Mb_GeRAuzQfQ,2545
|
|
9
9
|
iplotx/art3d/edge/__init__.py,sha256=uw1U_mMXqcZAvea-7JbU1PUKULQD1CMMrbwY02tiWRQ,8529
|
|
10
10
|
iplotx/art3d/edge/arrow.py,sha256=14BFXY9kDOUGPZl2fMD9gRVGyaaN5kyd-l6ikBg6WHU,3601
|
|
11
11
|
iplotx/art3d/edge/geometry.py,sha256=76VUmpPG-4Mls7x_994dMwdDPrWWnjT7nHJsHfwK_hA,2467
|
|
12
|
-
iplotx/edge/__init__.py,sha256=
|
|
12
|
+
iplotx/edge/__init__.py,sha256=8iHtkrEaxseh308VWlTgbZ7OJECqkRwSq-oJzejxH5U,26837
|
|
13
13
|
iplotx/edge/arrow.py,sha256=ymup2YT_0GVYMtZw_DSKrZqFHG_ysYteEhmoL6T8Mu4,17563
|
|
14
|
-
iplotx/edge/geometry.py,sha256=
|
|
14
|
+
iplotx/edge/geometry.py,sha256=dczXCLCn5vlFm_SwXXh_gwf0h7irILSCdNIDKTuuqmA,19646
|
|
15
15
|
iplotx/edge/leaf.py,sha256=SyGMv2PIOoH0pey8-aMVaZheK3hNe1Qz_okcyWbc4E4,4268
|
|
16
16
|
iplotx/edge/ports.py,sha256=BpkbiEhX4mPBBAhOv4jcKFG4Y8hxXz5GRtVLCC0jbtI,1235
|
|
17
17
|
iplotx/ingest/__init__.py,sha256=k1Q-7lSdotMR4RkF1x0t19RFsTknohX0L507Dw69WyU,5035
|
|
@@ -30,11 +30,11 @@ iplotx/ingest/providers/tree/skbio.py,sha256=T3IbOBut98A2GoGJzo6Tzp108uFa9n485mV
|
|
|
30
30
|
iplotx/layout/__init__.py,sha256=7on7I9CcbByz4X4hNJGsCc0GDFr3ul1K2UvTyvcGAWo,107
|
|
31
31
|
iplotx/layout/tree/__init__.py,sha256=hxASc8uXMWbpxnEHnChMzb3VQTTIyU4ww7SQRez1hK0,2000
|
|
32
32
|
iplotx/layout/tree/rooted.py,sha256=j3Y_Yd3YCWJRnhfE1qwEX5xCyUgvOJCX9Qfkc81B5BM,4215
|
|
33
|
-
iplotx/layout/tree/unrooted.py,sha256=
|
|
33
|
+
iplotx/layout/tree/unrooted.py,sha256=7iWLcQLwPsZgJpNlAnVKnEj-XI3twpYZhe4WztU9gEc,14668
|
|
34
34
|
iplotx/network/__init__.py,sha256=cJ6m6s157AOCqg-znUAlsumuZ2jiE9QsVQ3-GCK01wo,13543
|
|
35
35
|
iplotx/network/groups.py,sha256=E_eYVXRHjv1DcyA4RupTkMa-rRFrIKkt9Rxn_Elw9Nc,6796
|
|
36
36
|
iplotx/style/__init__.py,sha256=rf1GutrE8hHUhCoe4FGKYX-aNtHuu_U-kYQnqUxZNrY,10282
|
|
37
|
-
iplotx/style/leaf_info.py,sha256=
|
|
37
|
+
iplotx/style/leaf_info.py,sha256=oBiOH-fHt_D2Nic2eYeE5nG5nLIE_0gSDNbLAYe5g2w,1029
|
|
38
38
|
iplotx/style/library.py,sha256=58Y8BlllGLsR4pQM7_PVCP5tH6_4GkchXZvJpqGHlcg,8534
|
|
39
39
|
iplotx/tree/__init__.py,sha256=sxVrxZcsDIZddBdtI-TxOs37FyeYbHBLYqkLPWs6c8M,31199
|
|
40
40
|
iplotx/tree/cascades.py,sha256=Wwqhy46QGeb4LNGUuz_-bgNWUMz6PFzs_dIxIb1dtqc,8394
|
|
@@ -43,6 +43,6 @@ iplotx/utils/geometry.py,sha256=6RrC6qaB0-1vIk1LhGA4CfsiMd-9JNniSPyL_l9mshE,9245
|
|
|
43
43
|
iplotx/utils/internal.py,sha256=WWfcZDGK8Ut1y_tOHRGg9wSqY1bwSeLQO7dHM_8Tvwo,107
|
|
44
44
|
iplotx/utils/matplotlib.py,sha256=p_53Oamof0RI4mtV8HrdDtZbgVqUxeUZ_KDvLZSiBUQ,8604
|
|
45
45
|
iplotx/utils/style.py,sha256=vyNP80nDYVinqm6_9ltCJCtjK35ZcGlHvOskNv3eQBc,4225
|
|
46
|
-
iplotx-1.
|
|
47
|
-
iplotx-1.
|
|
48
|
-
iplotx-1.
|
|
46
|
+
iplotx-1.4.1.dist-info/METADATA,sha256=8ZAla76ojuZ1bfzsj3gTS-agA44OIz0Ze18Hc-p3Gcg,5407
|
|
47
|
+
iplotx-1.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
48
|
+
iplotx-1.4.1.dist-info/RECORD,,
|
|
File without changes
|