iplotx 0.4.0__py3-none-any.whl → 0.5.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/cascades.py +19 -28
- iplotx/edge/__init__.py +74 -6
- iplotx/edge/arrow.py +10 -3
- iplotx/edge/geometry.py +5 -17
- iplotx/edge/ports.py +3 -2
- iplotx/groups.py +1 -3
- iplotx/ingest/__init__.py +5 -14
- iplotx/ingest/heuristics.py +1 -4
- iplotx/ingest/providers/network/igraph.py +4 -12
- iplotx/ingest/providers/network/networkx.py +6 -20
- iplotx/ingest/providers/network/simple.py +2 -9
- iplotx/ingest/providers/tree/biopython.py +2 -5
- iplotx/ingest/providers/tree/cogent3.py +2 -5
- iplotx/ingest/providers/tree/ete4.py +2 -5
- iplotx/ingest/providers/tree/simple.py +97 -0
- iplotx/ingest/providers/tree/skbio.py +2 -5
- iplotx/ingest/typing.py +5 -14
- iplotx/network.py +4 -7
- iplotx/plotting.py +1 -1
- iplotx/style/__init__.py +131 -106
- iplotx/style/leaf_info.py +3 -0
- iplotx/style/library.py +94 -0
- iplotx/tree.py +55 -42
- iplotx/typing.py +2 -0
- iplotx/utils/geometry.py +32 -40
- iplotx/utils/matplotlib.py +13 -10
- iplotx/utils/style.py +6 -1
- iplotx/version.py +1 -1
- iplotx/vertex.py +16 -23
- {iplotx-0.4.0.dist-info → iplotx-0.5.1.dist-info}/METADATA +33 -15
- iplotx-0.5.1.dist-info/RECORD +38 -0
- iplotx-0.4.0.dist-info/RECORD +0 -37
- {iplotx-0.4.0.dist-info → iplotx-0.5.1.dist-info}/WHEEL +0 -0
iplotx/cascades.py
CHANGED
|
@@ -2,6 +2,7 @@ from typing import (
|
|
|
2
2
|
Any,
|
|
3
3
|
Optional,
|
|
4
4
|
)
|
|
5
|
+
import warnings
|
|
5
6
|
import numpy as np
|
|
6
7
|
import pandas as pd
|
|
7
8
|
|
|
@@ -38,7 +39,7 @@ class CascadeCollection(mpl.collections.PatchCollection):
|
|
|
38
39
|
|
|
39
40
|
# NOTE: there is a weird bug in pandas when using generic Hashable-s
|
|
40
41
|
# with .loc. Seems like doing .T[...] works for individual index
|
|
41
|
-
# elements only though
|
|
42
|
+
# elements only though (i.e. using __getitem__ a la dict)
|
|
42
43
|
def get_node_coords(node):
|
|
43
44
|
return layout.T[node].values
|
|
44
45
|
|
|
@@ -54,9 +55,19 @@ class CascadeCollection(mpl.collections.PatchCollection):
|
|
|
54
55
|
# These patches need at least a facecolor (usually) or an edgecolor
|
|
55
56
|
# so it's safe to make a list from these
|
|
56
57
|
nodes_unordered = set()
|
|
57
|
-
for prop in ("facecolor", "edgecolor"):
|
|
58
|
+
for prop in ("facecolor", "edgecolor", "linewidth", "linestyle"):
|
|
58
59
|
if prop in style:
|
|
59
|
-
|
|
60
|
+
value = style[prop]
|
|
61
|
+
if isinstance(value, dict):
|
|
62
|
+
nodes_unordered |= set(value.keys())
|
|
63
|
+
|
|
64
|
+
if len(nodes_unordered) == 0:
|
|
65
|
+
warnings.warn(
|
|
66
|
+
"No nodes found in the style for the cascading patches. "
|
|
67
|
+
"Please provide a style with at least one dict-like "
|
|
68
|
+
"specification among the following properties: 'facecolor', "
|
|
69
|
+
"'edgecolor', 'color', 'linewidth', or 'linestyle'.",
|
|
70
|
+
)
|
|
60
71
|
|
|
61
72
|
# Draw the patches from the closest to the root (earlier drawing)
|
|
62
73
|
# to the closer to the leaves (later drawing).
|
|
@@ -70,32 +81,15 @@ class CascadeCollection(mpl.collections.PatchCollection):
|
|
|
70
81
|
f"Cascading patches not implemented for layout: {layout_name}.",
|
|
71
82
|
)
|
|
72
83
|
|
|
73
|
-
nleaves = sum(1 for leaf in provider(tree).get_leaves())
|
|
74
|
-
extend_mode = style.get("extend", False)
|
|
75
|
-
if extend_mode and (extend_mode != "leaf_labels"):
|
|
76
|
-
if layout_name == "horizontal":
|
|
77
|
-
if orientation == "right":
|
|
78
|
-
maxdepth = layout.values[:, 0].max()
|
|
79
|
-
else:
|
|
80
|
-
maxdepth = layout.values[:, 0].min()
|
|
81
|
-
elif layout_name == "vertical":
|
|
82
|
-
if orientation == "descending":
|
|
83
|
-
maxdepth = layout.values[:, 1].min()
|
|
84
|
-
else:
|
|
85
|
-
maxdepth = layout.values[:, 1].max()
|
|
86
|
-
elif layout_name == "radial":
|
|
87
|
-
# layout values are: r, theta
|
|
88
|
-
maxdepth = layout.values[:, 0].max()
|
|
89
84
|
self._maxdepth = maxdepth
|
|
90
85
|
|
|
91
86
|
cascading_patches = []
|
|
87
|
+
nleaves = sum(1 for leaf in provider(tree).get_leaves())
|
|
92
88
|
for node in drawing_order:
|
|
93
89
|
stylei = rotate_style(style, key=node)
|
|
94
90
|
stylei.pop("extend", None)
|
|
95
91
|
# Default alpha is 0.5 for simple colors
|
|
96
|
-
if isinstance(stylei.get("facecolor", None), str) and (
|
|
97
|
-
"alpha" not in stylei
|
|
98
|
-
):
|
|
92
|
+
if isinstance(stylei.get("facecolor", None), str) and ("alpha" not in stylei):
|
|
99
93
|
stylei["alpha"] = 0.5
|
|
100
94
|
|
|
101
95
|
provider_node = provider(node)
|
|
@@ -137,9 +131,7 @@ class CascadeCollection(mpl.collections.PatchCollection):
|
|
|
137
131
|
rmax = maxdepth if extend else leaves_coords[:, 0].max()
|
|
138
132
|
thetamin = leaves_coords[:, 1].min() - 0.5 * dtheta
|
|
139
133
|
thetamax = leaves_coords[:, 1].max() + 0.5 * dtheta
|
|
140
|
-
thetas = np.linspace(
|
|
141
|
-
thetamin, thetamax, max(30, (thetamax - thetamin) // 3)
|
|
142
|
-
)
|
|
134
|
+
thetas = np.linspace(thetamin, thetamax, max(30, (thetamax - thetamin) // 3))
|
|
143
135
|
xs = list(rmin * np.cos(thetas)) + list(rmax * np.cos(thetas[::-1]))
|
|
144
136
|
ys = list(rmin * np.sin(thetas)) + list(rmax * np.sin(thetas[::-1]))
|
|
145
137
|
points = list(zip(xs, ys))
|
|
@@ -200,9 +192,8 @@ class CascadeCollection(mpl.collections.PatchCollection):
|
|
|
200
192
|
for path in self.get_paths():
|
|
201
193
|
# Old radii
|
|
202
194
|
r2old = np.linalg.norm(path.vertices[-2])
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)
|
|
195
|
+
# Update the outer part of the wedge patch
|
|
196
|
+
path.vertices[(len(path.vertices) - 1) // 2 :] *= self.get_maxdepth() / r2old
|
|
206
197
|
return
|
|
207
198
|
|
|
208
199
|
if (layout_name, orientation) == ("horizontal", "right"):
|
iplotx/edge/__init__.py
CHANGED
|
@@ -121,8 +121,47 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
121
121
|
transform=transform,
|
|
122
122
|
)
|
|
123
123
|
|
|
124
|
+
if "split" in self._style:
|
|
125
|
+
self._add_subedges(
|
|
126
|
+
len(patches),
|
|
127
|
+
self._style["split"],
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def _add_subedges(
|
|
131
|
+
self,
|
|
132
|
+
nedges,
|
|
133
|
+
style,
|
|
134
|
+
):
|
|
135
|
+
"""Add subedges to shadow the current edges."""
|
|
136
|
+
segments = [np.zeros((2, 2)) for i in range(nedges)]
|
|
137
|
+
kwargs = {
|
|
138
|
+
"linewidths": [],
|
|
139
|
+
"edgecolors": [],
|
|
140
|
+
"linestyles": [],
|
|
141
|
+
}
|
|
142
|
+
for i in range(nedges):
|
|
143
|
+
vids = self._vertex_ids[i]
|
|
144
|
+
stylei = rotate_style(style, index=i, key=vids, key2=vids[-1])
|
|
145
|
+
for key, values in kwargs.items():
|
|
146
|
+
# iplotx uses singular style properties
|
|
147
|
+
key = key.rstrip("s")
|
|
148
|
+
# "color" has higher priority than "edgecolor"
|
|
149
|
+
if (key == "edgecolor") and ("color" in stylei):
|
|
150
|
+
val = stylei["color"]
|
|
151
|
+
else:
|
|
152
|
+
val = stylei.get(key.rstrip("s"), getattr(self, f"get_{key}")()[i])
|
|
153
|
+
values.append(val)
|
|
154
|
+
|
|
155
|
+
self._subedges = mpl.collections.LineCollection(
|
|
156
|
+
segments,
|
|
157
|
+
transform=self.get_transform(),
|
|
158
|
+
**kwargs,
|
|
159
|
+
)
|
|
160
|
+
|
|
124
161
|
def get_children(self) -> tuple:
|
|
125
162
|
children = []
|
|
163
|
+
if hasattr(self, "_subedges"):
|
|
164
|
+
children.append(self._subedges)
|
|
126
165
|
if hasattr(self, "_arrows"):
|
|
127
166
|
children.append(self._arrows)
|
|
128
167
|
if hasattr(self, "_label_collection"):
|
|
@@ -209,7 +248,7 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
209
248
|
index = pd.Series(
|
|
210
249
|
np.arange(len(index)),
|
|
211
250
|
index=index,
|
|
212
|
-
)
|
|
251
|
+
).to_dict()
|
|
213
252
|
|
|
214
253
|
voffsets = []
|
|
215
254
|
vpaths = []
|
|
@@ -298,10 +337,20 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
298
337
|
tension = 0
|
|
299
338
|
ports = None
|
|
300
339
|
|
|
340
|
+
# False is a synonym for "none"
|
|
301
341
|
waypoints = edge_stylei.get("waypoints", "none")
|
|
342
|
+
if waypoints is False or waypoints is np.False_:
|
|
343
|
+
waypoints = "none"
|
|
344
|
+
elif waypoints is True or waypoints is np.True_:
|
|
345
|
+
raise ValueError(
|
|
346
|
+
"Could not determine automatically type of edge waypoints.",
|
|
347
|
+
)
|
|
302
348
|
if waypoints != "none":
|
|
303
349
|
ports = edge_stylei.get("ports", (None, None))
|
|
304
350
|
|
|
351
|
+
if not isinstance(waypoints, str):
|
|
352
|
+
__import__("ipdb").set_trace()
|
|
353
|
+
|
|
305
354
|
# Compute actual edge path
|
|
306
355
|
path, angles = _compute_edge_path(
|
|
307
356
|
vcoord_data,
|
|
@@ -331,6 +380,17 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
331
380
|
if (offset != 0).any():
|
|
332
381
|
path.vertices[:] = trans_inv(trans(path.vertices) + offset)
|
|
333
382
|
|
|
383
|
+
# If splitting is active, split the path here, shedding off the last straight
|
|
384
|
+
# segment but only if waypoints were used
|
|
385
|
+
if hasattr(self, "_subedges") and waypoints != "none":
|
|
386
|
+
# NOTE: we are already in the middle of a redraw, so we can happily avoid
|
|
387
|
+
# causing stale of the subedges. They are already scheduled to be redrawn
|
|
388
|
+
# at the end of this function.
|
|
389
|
+
self._subedges._paths[i].vertices[:] = path.vertices[-2:].copy()
|
|
390
|
+
# NOTE: instead of shortening the path, we just make the last bit invisible
|
|
391
|
+
# that makes it easier on memory management etc.
|
|
392
|
+
path.vertices[-1] = path.vertices[-2]
|
|
393
|
+
|
|
334
394
|
# Collect angles for this vertex, to be used for loops plotting below
|
|
335
395
|
if vinfo.get("loops", True):
|
|
336
396
|
if v1 in loop_vertex_dict:
|
|
@@ -399,6 +459,9 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
399
459
|
idx += 1
|
|
400
460
|
|
|
401
461
|
self._paths = paths
|
|
462
|
+
# FIXME:??
|
|
463
|
+
# if hasattr(self, "_subedges"):
|
|
464
|
+
# self._subedges.stale = True
|
|
402
465
|
|
|
403
466
|
def _update_labels(self):
|
|
404
467
|
if self._labels is None:
|
|
@@ -463,13 +526,13 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
463
526
|
if not self.get_visible():
|
|
464
527
|
return
|
|
465
528
|
|
|
529
|
+
# This includes the subedges if present
|
|
466
530
|
self._update_paths()
|
|
467
531
|
# This sets the arrow offsets
|
|
468
532
|
self._update_children()
|
|
469
533
|
|
|
470
534
|
super().draw(renderer)
|
|
471
535
|
for child in self.get_children():
|
|
472
|
-
# This sets the arrow sizes with dpi scaling
|
|
473
536
|
child.draw(renderer)
|
|
474
537
|
|
|
475
538
|
def get_ports(self) -> Optional[LeafProperty[Pair[Optional[str]]]]:
|
|
@@ -489,7 +552,8 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
489
552
|
edge end.
|
|
490
553
|
"""
|
|
491
554
|
if ports is None:
|
|
492
|
-
|
|
555
|
+
if "ports" in self._style:
|
|
556
|
+
del self._style["ports"]
|
|
493
557
|
else:
|
|
494
558
|
self._style["ports"] = ports
|
|
495
559
|
self.stale = True
|
|
@@ -523,7 +587,8 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
523
587
|
|
|
524
588
|
"""
|
|
525
589
|
if tension is None:
|
|
526
|
-
|
|
590
|
+
if "tension" in self._style:
|
|
591
|
+
del self._style["tension"]
|
|
527
592
|
else:
|
|
528
593
|
self._style["tension"] = tension
|
|
529
594
|
self.stale = True
|
|
@@ -583,7 +648,8 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
583
648
|
looptension: The tension to use for loops. If None, the default is 2.5.
|
|
584
649
|
"""
|
|
585
650
|
if looptension is None:
|
|
586
|
-
|
|
651
|
+
if "looptension" in self._style:
|
|
652
|
+
del self._style["looptension"]
|
|
587
653
|
else:
|
|
588
654
|
self._style["looptension"] = looptension
|
|
589
655
|
self.stale = True
|
|
@@ -603,7 +669,8 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
603
669
|
offset: The offset in points for parallel straight edges. If None, the default is 3.
|
|
604
670
|
"""
|
|
605
671
|
if offset is None:
|
|
606
|
-
|
|
672
|
+
if "offset" in self._style:
|
|
673
|
+
del self._style["offset"]
|
|
607
674
|
else:
|
|
608
675
|
self._style["offset"] = offset
|
|
609
676
|
self.stale = True
|
|
@@ -634,6 +701,7 @@ def make_stub_patch(**kwargs):
|
|
|
634
701
|
"paralleloffset",
|
|
635
702
|
"cmap",
|
|
636
703
|
"norm",
|
|
704
|
+
"split",
|
|
637
705
|
]
|
|
638
706
|
for prop in forbidden_props:
|
|
639
707
|
if prop in kwargs:
|
iplotx/edge/arrow.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Module for edge arrows in iplotx.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from typing import Never
|
|
6
|
+
|
|
5
7
|
import numpy as np
|
|
6
8
|
import matplotlib as mpl
|
|
7
9
|
from matplotlib.patches import PathPatch
|
|
@@ -124,12 +126,17 @@ class EdgeArrowCollection(mpl.collections.PatchCollection):
|
|
|
124
126
|
|
|
125
127
|
return patches, sizes
|
|
126
128
|
|
|
127
|
-
def set_array(self, A):
|
|
129
|
+
def set_array(self, A: np.ndarray) -> Never:
|
|
128
130
|
"""Set the array for cmap/norm coloring, but keep the facecolors as set (usually 'none')."""
|
|
129
131
|
raise ValueError("Setting an array for arrows directly is not supported.")
|
|
130
132
|
|
|
131
|
-
def set_colors(self, colors):
|
|
132
|
-
"""Set arrow colors (edge and/or face) based on a colormap.
|
|
133
|
+
def set_colors(self, colors: np.ndarray) -> None:
|
|
134
|
+
"""Set arrow colors (edge and/or face) based on a colormap.
|
|
135
|
+
|
|
136
|
+
Parameters:
|
|
137
|
+
colors: Color array to apply. This must be an Nx3 or Nx4 vector of RGB or RGBA colors.
|
|
138
|
+
This function will NOT attempt to convert other color descriptions to RGB/RGBA.
|
|
139
|
+
"""
|
|
133
140
|
# NOTE: facecolors is always an array because we come from patches
|
|
134
141
|
# It can have zero alpha (i.e. if we choose "none", or a hollow marker)
|
|
135
142
|
self.set_edgecolor(colors)
|
iplotx/edge/geometry.py
CHANGED
|
@@ -204,9 +204,7 @@ def _compute_edge_path_straight(
|
|
|
204
204
|
points.append(vs)
|
|
205
205
|
|
|
206
206
|
# Shorten at end vertex
|
|
207
|
-
ve = (
|
|
208
|
-
_get_shorter_edge_coords(vpath_fig[1], vsize_fig[1], theta + pi) + vcoord_fig[1]
|
|
209
|
-
)
|
|
207
|
+
ve = _get_shorter_edge_coords(vpath_fig[1], vsize_fig[1], theta + pi) + vcoord_fig[1]
|
|
210
208
|
points.append(ve)
|
|
211
209
|
|
|
212
210
|
codes = ["MOVETO", "LINETO"]
|
|
@@ -230,7 +228,6 @@ def _compute_edge_path_waypoints(
|
|
|
230
228
|
ports: Pair[Optional[str]] = (None, None),
|
|
231
229
|
**kwargs,
|
|
232
230
|
):
|
|
233
|
-
|
|
234
231
|
if waypoints in ("x0y1", "y0x1"):
|
|
235
232
|
assert layout_coordinate_system == "cartesian"
|
|
236
233
|
|
|
@@ -253,8 +250,7 @@ def _compute_edge_path_waypoints(
|
|
|
253
250
|
|
|
254
251
|
# Shorten at vertex border
|
|
255
252
|
vshorts[i] = (
|
|
256
|
-
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i])
|
|
257
|
-
+ vcoord_fig[i]
|
|
253
|
+
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i]) + vcoord_fig[i]
|
|
258
254
|
)
|
|
259
255
|
|
|
260
256
|
# Shorten waypoints to keep the angles right
|
|
@@ -302,10 +298,7 @@ def _compute_edge_path_waypoints(
|
|
|
302
298
|
theta = atan2(*(_get_port_unit_vector(ports[i], trans_inv)[::-1]))
|
|
303
299
|
|
|
304
300
|
# Shorten at vertex border
|
|
305
|
-
vshort = (
|
|
306
|
-
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], theta)
|
|
307
|
-
+ vcoord_fig[i]
|
|
308
|
-
)
|
|
301
|
+
vshort = _get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], theta) + vcoord_fig[i]
|
|
309
302
|
thetas.append(theta)
|
|
310
303
|
vshorts.append(vshort)
|
|
311
304
|
|
|
@@ -324,9 +317,7 @@ def _compute_edge_path_waypoints(
|
|
|
324
317
|
|
|
325
318
|
betas = np.linspace(alpha0, alpha1, points_per_curve)
|
|
326
319
|
waypoints = [r0, r1][idx_inner] * np.vstack([np.cos(betas), np.sin(betas)]).T
|
|
327
|
-
endpoint = [r0, r1][idx_outer] * np.array(
|
|
328
|
-
[np.cos(alpha_outer), np.sin(alpha_outer)]
|
|
329
|
-
)
|
|
320
|
+
endpoint = [r0, r1][idx_outer] * np.array([np.cos(alpha_outer), np.sin(alpha_outer)])
|
|
330
321
|
points = np.array(list(waypoints) + [endpoint])
|
|
331
322
|
points = trans(points)
|
|
332
323
|
codes = ["MOVETO"] + ["LINETO"] * len(waypoints)
|
|
@@ -406,10 +397,7 @@ def _compute_edge_path_curved(
|
|
|
406
397
|
thetas = [None, None]
|
|
407
398
|
for i in range(2):
|
|
408
399
|
thetas[i] = atan2(*((auxs[i] - vcoord_fig[i])[::-1]))
|
|
409
|
-
vs[i] = (
|
|
410
|
-
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i])
|
|
411
|
-
+ vcoord_fig[i]
|
|
412
|
-
)
|
|
400
|
+
vs[i] = _get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i]) + vcoord_fig[i]
|
|
413
401
|
|
|
414
402
|
path = {
|
|
415
403
|
"vertices": [
|
iplotx/edge/ports.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Module for handling edge ports in iplotx.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from collections.abc import Callable
|
|
5
6
|
import numpy as np
|
|
6
7
|
|
|
7
8
|
sq2 = np.sqrt(2) / 2
|
|
@@ -19,8 +20,8 @@ port_dict = {
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def _get_port_unit_vector(
|
|
22
|
-
portstring,
|
|
23
|
-
trans_inv,
|
|
23
|
+
portstring: str,
|
|
24
|
+
trans_inv: Callable,
|
|
24
25
|
):
|
|
25
26
|
"""Get the tangent unit vector from a port string."""
|
|
26
27
|
# The only tricky bit is if the port says e.g. north but the y axis is inverted, in which
|
iplotx/groups.py
CHANGED
|
@@ -130,14 +130,12 @@ class GroupingArtist(PatchCollection):
|
|
|
130
130
|
return patches, grouping, coords_hulls
|
|
131
131
|
|
|
132
132
|
def _compute_paths(self, dpi: float = 72.0) -> None:
|
|
133
|
-
ppc = self._points_per_curve
|
|
134
133
|
for i, hull in enumerate(self._coords_hulls):
|
|
135
|
-
|
|
134
|
+
_compute_group_path_with_vertex_padding(
|
|
136
135
|
hull,
|
|
137
136
|
self._paths[i].vertices,
|
|
138
137
|
self.get_transform(),
|
|
139
138
|
vertexpadding=self.get_vertexpadding_dpi(dpi),
|
|
140
|
-
points_per_curve=ppc,
|
|
141
139
|
)
|
|
142
140
|
|
|
143
141
|
def _process(self) -> None:
|
iplotx/ingest/__init__.py
CHANGED
|
@@ -32,15 +32,11 @@ provider_protocols = {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
# Internally supported data providers
|
|
35
|
-
data_providers: dict[str, dict[str, Protocol]] = {
|
|
36
|
-
kind: {} for kind in provider_protocols
|
|
37
|
-
}
|
|
35
|
+
data_providers: dict[str, dict[str, Protocol]] = {kind: {} for kind in provider_protocols}
|
|
38
36
|
for kind in data_providers:
|
|
39
37
|
providers_path = pathlib.Path(__file__).parent.joinpath("providers").joinpath(kind)
|
|
40
38
|
for importer, module_name, _ in pkgutil.iter_modules([providers_path]):
|
|
41
|
-
module = importlib.import_module(
|
|
42
|
-
f"iplotx.ingest.providers.{kind}.{module_name}"
|
|
43
|
-
)
|
|
39
|
+
module = importlib.import_module(f"iplotx.ingest.providers.{kind}.{module_name}")
|
|
44
40
|
for key, val in module.__dict__.items():
|
|
45
41
|
if key == provider_protocols[kind].__name__:
|
|
46
42
|
continue
|
|
@@ -123,8 +119,7 @@ def ingest_tree_data(
|
|
|
123
119
|
else:
|
|
124
120
|
sup = ", ".join(data_providers["tree"].keys())
|
|
125
121
|
raise ValueError(
|
|
126
|
-
f"Tree library '{tl}' is not installed. "
|
|
127
|
-
f"Currently installed supported libraries: {sup}."
|
|
122
|
+
f"Tree library '{tl}' is not installed. Currently installed supported libraries: {sup}."
|
|
128
123
|
)
|
|
129
124
|
|
|
130
125
|
result = provider(
|
|
@@ -145,14 +140,10 @@ def ingest_tree_data(
|
|
|
145
140
|
# INTERNAL FUNCTIONS
|
|
146
141
|
def _update_data_providers(kind):
|
|
147
142
|
"""Update data provieders dynamically from external packages."""
|
|
148
|
-
discovered_providers = importlib.metadata.entry_points(
|
|
149
|
-
group=f"iplotx.{kind}_data_providers"
|
|
150
|
-
)
|
|
143
|
+
discovered_providers = importlib.metadata.entry_points(group=f"iplotx.{kind}_data_providers")
|
|
151
144
|
for entry_point in discovered_providers:
|
|
152
145
|
if entry_point.name not in data_providers["network"]:
|
|
153
146
|
try:
|
|
154
147
|
data_providers[kind][entry_point.name] = entry_point.load()
|
|
155
148
|
except Exception as e:
|
|
156
|
-
warnings.warn(
|
|
157
|
-
f"Failed to load {kind} data provider '{entry_point.name}': {e}"
|
|
158
|
-
)
|
|
149
|
+
warnings.warn(f"Failed to load {kind} data provider '{entry_point.name}': {e}")
|
iplotx/ingest/heuristics.py
CHANGED
|
@@ -3,7 +3,6 @@ Heuristics module to funnel certain variable inputs (e.g. layouts) into a standa
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from typing import (
|
|
6
|
-
Optional,
|
|
7
6
|
Any,
|
|
8
7
|
)
|
|
9
8
|
from collections.abc import Hashable
|
|
@@ -79,9 +78,7 @@ def normalise_tree_layout(
|
|
|
79
78
|
if isinstance(layout, str):
|
|
80
79
|
layout = compute_tree_layout(layout, **kwargs)
|
|
81
80
|
else:
|
|
82
|
-
raise NotImplementedError(
|
|
83
|
-
"Only internally computed tree layout currently accepted."
|
|
84
|
-
)
|
|
81
|
+
raise NotImplementedError("Only internally computed tree layout currently accepted.")
|
|
85
82
|
|
|
86
83
|
if isinstance(layout, dict):
|
|
87
84
|
# Adjust vertex layout
|
|
@@ -3,11 +3,11 @@ from typing import (
|
|
|
3
3
|
Sequence,
|
|
4
4
|
)
|
|
5
5
|
from collections.abc import Hashable
|
|
6
|
+
import importlib
|
|
6
7
|
import numpy as np
|
|
7
8
|
import pandas as pd
|
|
8
9
|
|
|
9
10
|
from ....typing import (
|
|
10
|
-
GraphType,
|
|
11
11
|
LayoutType,
|
|
12
12
|
)
|
|
13
13
|
from ...heuristics import (
|
|
@@ -51,9 +51,7 @@ class IGraphDataProvider(NetworkDataProvider):
|
|
|
51
51
|
if np.isscalar(vertex_labels):
|
|
52
52
|
vertex_df["label"] = vertex_df.index.astype(str)
|
|
53
53
|
elif len(vertex_labels) != len(vertex_df):
|
|
54
|
-
raise ValueError(
|
|
55
|
-
"Vertex labels must be the same length as the number of vertices."
|
|
56
|
-
)
|
|
54
|
+
raise ValueError("Vertex labels must be the same length as the number of vertices.")
|
|
57
55
|
else:
|
|
58
56
|
vertex_df["label"] = vertex_labels
|
|
59
57
|
|
|
@@ -72,9 +70,7 @@ class IGraphDataProvider(NetworkDataProvider):
|
|
|
72
70
|
# Edge labels
|
|
73
71
|
if edge_labels is not None:
|
|
74
72
|
if len(edge_labels) != len(edge_df):
|
|
75
|
-
raise ValueError(
|
|
76
|
-
"Edge labels must be the same length as the number of edges."
|
|
77
|
-
)
|
|
73
|
+
raise ValueError("Edge labels must be the same length as the number of edges.")
|
|
78
74
|
edge_df["label"] = edge_labels
|
|
79
75
|
|
|
80
76
|
network_data = {
|
|
@@ -87,11 +83,7 @@ class IGraphDataProvider(NetworkDataProvider):
|
|
|
87
83
|
|
|
88
84
|
@staticmethod
|
|
89
85
|
def check_dependencies() -> bool:
|
|
90
|
-
|
|
91
|
-
import igraph
|
|
92
|
-
except ImportError:
|
|
93
|
-
return False
|
|
94
|
-
return True
|
|
86
|
+
return importlib.util.find_spec("igraph") is not None
|
|
95
87
|
|
|
96
88
|
@staticmethod
|
|
97
89
|
def graph_type():
|
|
@@ -3,11 +3,11 @@ from typing import (
|
|
|
3
3
|
Sequence,
|
|
4
4
|
)
|
|
5
5
|
from collections.abc import Hashable
|
|
6
|
+
import importlib
|
|
6
7
|
import numpy as np
|
|
7
8
|
import pandas as pd
|
|
8
9
|
|
|
9
10
|
from ....typing import (
|
|
10
|
-
GraphType,
|
|
11
11
|
LayoutType,
|
|
12
12
|
)
|
|
13
13
|
from ...heuristics import (
|
|
@@ -64,21 +64,13 @@ class NetworkXDataProvider(NetworkDataProvider):
|
|
|
64
64
|
if "label" in vertex_df:
|
|
65
65
|
del vertex_df["label"]
|
|
66
66
|
else:
|
|
67
|
-
if (
|
|
68
|
-
np.isscalar(vertex_labels)
|
|
69
|
-
and (not vertex_labels)
|
|
70
|
-
and ("label" in vertex_df)
|
|
71
|
-
):
|
|
67
|
+
if np.isscalar(vertex_labels) and (not vertex_labels) and ("label" in vertex_df):
|
|
72
68
|
del vertex_df["label"]
|
|
73
69
|
elif vertex_labels is True:
|
|
74
70
|
if "label" not in vertex_df:
|
|
75
71
|
vertex_df["label"] = vertex_df.index
|
|
76
|
-
elif (not np.isscalar(vertex_labels)) and (
|
|
77
|
-
|
|
78
|
-
):
|
|
79
|
-
raise ValueError(
|
|
80
|
-
"Vertex labels must be the same length as the number of vertices."
|
|
81
|
-
)
|
|
72
|
+
elif (not np.isscalar(vertex_labels)) and (len(vertex_labels) != len(vertex_df)):
|
|
73
|
+
raise ValueError("Vertex labels must be the same length as the number of vertices.")
|
|
82
74
|
elif isinstance(vertex_labels, nx.classes.reportviews.NodeDataView):
|
|
83
75
|
vertex_df["label"] = pd.Series(dict(vertex_labels))
|
|
84
76
|
else:
|
|
@@ -108,9 +100,7 @@ class NetworkXDataProvider(NetworkDataProvider):
|
|
|
108
100
|
edge_df["label"] = [str(i) for i in edge_df.index]
|
|
109
101
|
else:
|
|
110
102
|
if len(edge_labels) != len(edge_df):
|
|
111
|
-
raise ValueError(
|
|
112
|
-
"Edge labels must be the same length as the number of edges."
|
|
113
|
-
)
|
|
103
|
+
raise ValueError("Edge labels must be the same length as the number of edges.")
|
|
114
104
|
edge_df["label"] = edge_labels
|
|
115
105
|
|
|
116
106
|
network_data = {
|
|
@@ -123,11 +113,7 @@ class NetworkXDataProvider(NetworkDataProvider):
|
|
|
123
113
|
|
|
124
114
|
@staticmethod
|
|
125
115
|
def check_dependencies() -> bool:
|
|
126
|
-
|
|
127
|
-
import networkx
|
|
128
|
-
except ImportError:
|
|
129
|
-
return False
|
|
130
|
-
return True
|
|
116
|
+
return importlib.util.find_spec("networkx") is not None
|
|
131
117
|
|
|
132
118
|
@staticmethod
|
|
133
119
|
def graph_type():
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
from typing import (
|
|
2
2
|
Optional,
|
|
3
3
|
Sequence,
|
|
4
|
-
Any,
|
|
5
4
|
)
|
|
6
5
|
from collections.abc import Hashable
|
|
7
6
|
import numpy as np
|
|
8
7
|
import pandas as pd
|
|
9
8
|
|
|
10
9
|
from ....typing import (
|
|
11
|
-
GraphType,
|
|
12
10
|
LayoutType,
|
|
13
11
|
)
|
|
14
|
-
from ...heuristics import (
|
|
15
|
-
normalise_layout,
|
|
16
|
-
)
|
|
17
12
|
from ...typing import (
|
|
18
13
|
NetworkDataProvider,
|
|
19
14
|
NetworkData,
|
|
@@ -23,7 +18,7 @@ from ....utils.internal import (
|
|
|
23
18
|
)
|
|
24
19
|
|
|
25
20
|
|
|
26
|
-
class
|
|
21
|
+
class SimpleNetworkDataProvider(NetworkDataProvider):
|
|
27
22
|
def __call__(
|
|
28
23
|
self,
|
|
29
24
|
layout: Optional[LayoutType] = None,
|
|
@@ -69,9 +64,7 @@ class SimpleDataProvider(NetworkDataProvider):
|
|
|
69
64
|
if np.isscalar(vertex_labels):
|
|
70
65
|
vertex_df["label"] = vertex_df.index.astype(str)
|
|
71
66
|
elif len(vertex_labels) != len(vertex_df):
|
|
72
|
-
raise ValueError(
|
|
73
|
-
"Vertex labels must be the same length as the number of vertices."
|
|
74
|
-
)
|
|
67
|
+
raise ValueError("Vertex labels must be the same length as the number of vertices.")
|
|
75
68
|
else:
|
|
76
69
|
vertex_df["label"] = vertex_labels
|
|
77
70
|
|
|
@@ -3,6 +3,7 @@ from typing import (
|
|
|
3
3
|
Optional,
|
|
4
4
|
Sequence,
|
|
5
5
|
)
|
|
6
|
+
import importlib
|
|
6
7
|
from functools import partialmethod
|
|
7
8
|
|
|
8
9
|
from ...typing import (
|
|
@@ -34,11 +35,7 @@ class BiopythonDataProvider(TreeDataProvider):
|
|
|
34
35
|
|
|
35
36
|
@staticmethod
|
|
36
37
|
def check_dependencies() -> bool:
|
|
37
|
-
|
|
38
|
-
from Bio import Phylo
|
|
39
|
-
except ImportError:
|
|
40
|
-
return False
|
|
41
|
-
return True
|
|
38
|
+
return importlib.util.find_spec("Bio") is not None
|
|
42
39
|
|
|
43
40
|
@staticmethod
|
|
44
41
|
def tree_type():
|
|
@@ -3,6 +3,7 @@ from typing import (
|
|
|
3
3
|
Optional,
|
|
4
4
|
Sequence,
|
|
5
5
|
)
|
|
6
|
+
import importlib
|
|
6
7
|
from ...typing import (
|
|
7
8
|
TreeDataProvider,
|
|
8
9
|
)
|
|
@@ -28,11 +29,7 @@ class Cogent3DataProvider(TreeDataProvider):
|
|
|
28
29
|
|
|
29
30
|
@staticmethod
|
|
30
31
|
def check_dependencies() -> bool:
|
|
31
|
-
|
|
32
|
-
import cogent3
|
|
33
|
-
except ImportError:
|
|
34
|
-
return False
|
|
35
|
-
return True
|
|
32
|
+
return importlib.util.find_spec("cogent3") is not None
|
|
36
33
|
|
|
37
34
|
@staticmethod
|
|
38
35
|
def tree_type():
|
|
@@ -3,6 +3,7 @@ from typing import (
|
|
|
3
3
|
Optional,
|
|
4
4
|
Sequence,
|
|
5
5
|
)
|
|
6
|
+
import importlib
|
|
6
7
|
from functools import partialmethod
|
|
7
8
|
|
|
8
9
|
from ...typing import (
|
|
@@ -31,11 +32,7 @@ class Ete4DataProvider(TreeDataProvider):
|
|
|
31
32
|
|
|
32
33
|
@staticmethod
|
|
33
34
|
def check_dependencies() -> bool:
|
|
34
|
-
|
|
35
|
-
from ete4 import Tree
|
|
36
|
-
except ImportError:
|
|
37
|
-
return False
|
|
38
|
-
return True
|
|
35
|
+
return importlib.util.find_spec("ete4") is not None
|
|
39
36
|
|
|
40
37
|
@staticmethod
|
|
41
38
|
def tree_type():
|