iplotx 0.2.1__py3-none-any.whl → 0.3.0__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 +223 -0
- iplotx/edge/__init__.py +20 -1
- iplotx/edge/geometry.py +72 -16
- iplotx/ingest/__init__.py +12 -4
- iplotx/ingest/heuristics.py +1 -3
- iplotx/ingest/providers/network/igraph.py +4 -2
- iplotx/ingest/providers/network/networkx.py +4 -2
- iplotx/ingest/providers/tree/biopython.py +21 -79
- iplotx/ingest/providers/tree/cogent3.py +17 -88
- iplotx/ingest/providers/tree/ete4.py +19 -87
- iplotx/ingest/providers/tree/skbio.py +17 -88
- iplotx/ingest/typing.py +225 -22
- iplotx/label.py +55 -8
- iplotx/layout.py +56 -35
- iplotx/plotting.py +6 -3
- iplotx/style.py +19 -5
- iplotx/tree.py +189 -8
- iplotx/version.py +1 -1
- iplotx/vertex.py +39 -7
- {iplotx-0.2.1.dist-info → iplotx-0.3.0.dist-info}/METADATA +2 -1
- iplotx-0.3.0.dist-info/RECORD +32 -0
- iplotx-0.2.1.dist-info/RECORD +0 -31
- {iplotx-0.2.1.dist-info → iplotx-0.3.0.dist-info}/WHEEL +0 -0
iplotx/tree.py
CHANGED
|
@@ -3,12 +3,14 @@ from typing import (
|
|
|
3
3
|
Sequence,
|
|
4
4
|
)
|
|
5
5
|
from collections.abc import Hashable
|
|
6
|
+
from collections import defaultdict
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
8
9
|
import pandas as pd
|
|
9
10
|
import matplotlib as mpl
|
|
10
11
|
|
|
11
12
|
from .style import (
|
|
13
|
+
context,
|
|
12
14
|
get_style,
|
|
13
15
|
rotate_style,
|
|
14
16
|
)
|
|
@@ -19,6 +21,7 @@ from .utils.matplotlib import (
|
|
|
19
21
|
)
|
|
20
22
|
from .ingest import (
|
|
21
23
|
ingest_tree_data,
|
|
24
|
+
data_providers,
|
|
22
25
|
)
|
|
23
26
|
from .vertex import (
|
|
24
27
|
VertexCollection,
|
|
@@ -30,6 +33,9 @@ from .edge import (
|
|
|
30
33
|
from .label import (
|
|
31
34
|
LabelCollection,
|
|
32
35
|
)
|
|
36
|
+
from .cascades import (
|
|
37
|
+
CascadeCollection,
|
|
38
|
+
)
|
|
33
39
|
from .network import (
|
|
34
40
|
_update_from_internal,
|
|
35
41
|
)
|
|
@@ -51,13 +57,14 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
51
57
|
def __init__(
|
|
52
58
|
self,
|
|
53
59
|
tree,
|
|
54
|
-
layout="horizontal",
|
|
55
|
-
orientation=
|
|
60
|
+
layout: Optional[str] = "horizontal",
|
|
61
|
+
orientation: Optional[str] = None,
|
|
56
62
|
directed: bool | str = False,
|
|
57
63
|
vertex_labels: Optional[
|
|
58
64
|
bool | list[str] | dict[Hashable, str] | pd.Series
|
|
59
65
|
] = None,
|
|
60
|
-
edge_labels: Optional[Sequence] = None,
|
|
66
|
+
edge_labels: Optional[Sequence | dict[Hashable, str] | pd.Series] = None,
|
|
67
|
+
leaf_labels: Optional[Sequence | dict[Hashable, str]] | pd.Series = None,
|
|
61
68
|
transform: mpl.transforms.Transform = mpl.transforms.IdentityTransform(),
|
|
62
69
|
offset_transform: Optional[mpl.transforms.Transform] = None,
|
|
63
70
|
):
|
|
@@ -73,6 +80,11 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
73
80
|
following choices: "parent" or "child".
|
|
74
81
|
vertex_labels: Labels for the vertices. Can be a list, dictionary, or pandas Series.
|
|
75
82
|
edge_labels: Labels for the edges. Can be a sequence of strings.
|
|
83
|
+
leaf_labels: Labels for the leaves. Can be a sequence of strings or a pandas Series.
|
|
84
|
+
These labels are positioned at the depth of the deepest leaf. If you want to
|
|
85
|
+
label leaves next to each leaf independently of how deep they are, use
|
|
86
|
+
the "vertex_labels" parameter instead - usually as a dict with the leaves
|
|
87
|
+
as keys and the labels as values.
|
|
76
88
|
transform: The transform to apply to the tree artist. This is usually the identity.
|
|
77
89
|
offset_transform: The offset transform to apply to the tree artist. This is
|
|
78
90
|
usually `ax.transData`.
|
|
@@ -84,8 +96,10 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
84
96
|
layout,
|
|
85
97
|
orientation=orientation,
|
|
86
98
|
directed=directed,
|
|
99
|
+
layout_style=get_style(".layout", {}),
|
|
87
100
|
vertex_labels=vertex_labels,
|
|
88
101
|
edge_labels=edge_labels,
|
|
102
|
+
leaf_labels=leaf_labels,
|
|
89
103
|
)
|
|
90
104
|
|
|
91
105
|
super().__init__()
|
|
@@ -101,6 +115,12 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
101
115
|
|
|
102
116
|
self._add_vertices()
|
|
103
117
|
self._add_edges()
|
|
118
|
+
self._add_leaf_vertices()
|
|
119
|
+
|
|
120
|
+
# NOTE: cascades need to be created after leaf vertices in case
|
|
121
|
+
# they are requested to wrap around them.
|
|
122
|
+
if "cascade" in self.get_vertices().get_style():
|
|
123
|
+
self._add_cascades()
|
|
104
124
|
|
|
105
125
|
def get_children(self) -> tuple[mpl.artist.Artist]:
|
|
106
126
|
"""Get the children of this artist.
|
|
@@ -108,7 +128,12 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
108
128
|
Returns:
|
|
109
129
|
The artists for vertices and edges.
|
|
110
130
|
"""
|
|
111
|
-
|
|
131
|
+
children = [self._vertices, self._edges]
|
|
132
|
+
if hasattr(self, "_leaf_vertices"):
|
|
133
|
+
children.append(self._leaf_vertices)
|
|
134
|
+
if hasattr(self, "_cascades"):
|
|
135
|
+
children.append(self._cascades)
|
|
136
|
+
return tuple(children)
|
|
112
137
|
|
|
113
138
|
def set_figure(self, fig) -> None:
|
|
114
139
|
"""Set the figure for this artist and its children.
|
|
@@ -120,6 +145,23 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
120
145
|
for child in self.get_children():
|
|
121
146
|
child.set_figure(fig)
|
|
122
147
|
|
|
148
|
+
# At the end, if there are cadcades with extent depending on
|
|
149
|
+
# leaf edges, we should update them
|
|
150
|
+
self._update_cascades_extent()
|
|
151
|
+
|
|
152
|
+
def _update_cascades_extent(self) -> None:
|
|
153
|
+
"""Update cascades if extent depends on leaf labels."""
|
|
154
|
+
if not hasattr(self, "_cascades"):
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
style_cascade = self.get_vertices().get_style()["cascade"]
|
|
158
|
+
extend_to_labels = style_cascade.get("extend", False) == "leaf_labels"
|
|
159
|
+
if not extend_to_labels:
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
maxdepth = self._get_maxdepth_leaf_labels()
|
|
163
|
+
self._cascades.set_maxdepth(maxdepth)
|
|
164
|
+
|
|
123
165
|
def get_offset_transform(self):
|
|
124
166
|
"""Get the offset transform (for vertices/edges)."""
|
|
125
167
|
return self._offset_transform
|
|
@@ -137,6 +179,16 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
137
179
|
if kind == "vertex":
|
|
138
180
|
layout = self._ipx_internal_data["vertex_df"][layout_columns]
|
|
139
181
|
return layout
|
|
182
|
+
elif kind == "leaf":
|
|
183
|
+
leaves = self._ipx_internal_data["leaf_df"].index
|
|
184
|
+
layout = self._ipx_internal_data["vertex_df"][layout_columns]
|
|
185
|
+
# NOTE: workaround for a pandas bug
|
|
186
|
+
idxs = []
|
|
187
|
+
for i, vid in enumerate(layout.index):
|
|
188
|
+
if vid in leaves:
|
|
189
|
+
idxs.append(i)
|
|
190
|
+
layout = layout.iloc[idxs]
|
|
191
|
+
return layout
|
|
140
192
|
|
|
141
193
|
elif kind == "edge":
|
|
142
194
|
return self._ipx_internal_data["edge_df"][layout_columns]
|
|
@@ -161,6 +213,14 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
161
213
|
edge_bbox = self._edges.get_datalim(transData)
|
|
162
214
|
bbox = mpl.transforms.Bbox.union([bbox, edge_bbox])
|
|
163
215
|
|
|
216
|
+
if hasattr(self, "_cascades"):
|
|
217
|
+
cascades_bbox = self._cascades.get_datalim(transData)
|
|
218
|
+
bbox = mpl.transforms.Bbox.union([bbox, cascades_bbox])
|
|
219
|
+
|
|
220
|
+
if hasattr(self, "_leaf_vertices"):
|
|
221
|
+
leaf_labels_bbox = self._leaf_vertices.get_datalim(transData)
|
|
222
|
+
bbox = mpl.transforms.Bbox.union([bbox, leaf_labels_bbox])
|
|
223
|
+
|
|
164
224
|
bbox = bbox.expanded(sw=(1.0 + pad), sh=(1.0 + pad))
|
|
165
225
|
return bbox
|
|
166
226
|
|
|
@@ -178,6 +238,12 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
178
238
|
"""Get EdgeCollection artist."""
|
|
179
239
|
return self._edges
|
|
180
240
|
|
|
241
|
+
def get_leaf_vertices(self) -> Optional[VertexCollection]:
|
|
242
|
+
"""Get leaf VertexCollection artist."""
|
|
243
|
+
if hasattr(self, "_leaf_vertices"):
|
|
244
|
+
return self._leaf_vertices
|
|
245
|
+
return None
|
|
246
|
+
|
|
181
247
|
def get_vertex_labels(self) -> LabelCollection:
|
|
182
248
|
"""Get list of vertex label artists."""
|
|
183
249
|
return self._vertices.get_labels()
|
|
@@ -186,6 +252,11 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
186
252
|
"""Get list of edge label artists."""
|
|
187
253
|
return self._edges.get_labels()
|
|
188
254
|
|
|
255
|
+
def get_leaf_labels(self) -> Optional[LabelCollection]:
|
|
256
|
+
if hasattr(self, "_leaf_vertices"):
|
|
257
|
+
return self._leaf_vertices.get_labels()
|
|
258
|
+
return None
|
|
259
|
+
|
|
189
260
|
def _add_vertices(self) -> None:
|
|
190
261
|
"""Add vertices to the tree."""
|
|
191
262
|
self._vertices = VertexCollection(
|
|
@@ -200,6 +271,101 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
200
271
|
offset_transform=self.get_offset_transform(),
|
|
201
272
|
)
|
|
202
273
|
|
|
274
|
+
def _add_leaf_vertices(self) -> None:
|
|
275
|
+
"""Add invisible deep vertices as leaf label anchors."""
|
|
276
|
+
leaf_layout = self.get_layout("leaf").copy()
|
|
277
|
+
# Set all to max depth
|
|
278
|
+
depth_idx = int(self._ipx_internal_data["layout_name"] == "vertical")
|
|
279
|
+
leaf_layout.iloc[:, depth_idx] = leaf_layout.iloc[:, depth_idx].max()
|
|
280
|
+
|
|
281
|
+
# Set invisible vertices with visible labels
|
|
282
|
+
layout_name = self._ipx_internal_data["layout_name"]
|
|
283
|
+
orientation = self._ipx_internal_data["orientation"]
|
|
284
|
+
if layout_name == "radial":
|
|
285
|
+
ha = "auto"
|
|
286
|
+
elif orientation in ("left", "ascending"):
|
|
287
|
+
ha = "right"
|
|
288
|
+
else:
|
|
289
|
+
ha = "left"
|
|
290
|
+
|
|
291
|
+
leaf_vertex_style = {
|
|
292
|
+
"size": 0,
|
|
293
|
+
"label": {
|
|
294
|
+
"verticalalignment": "center",
|
|
295
|
+
"horizontalalignment": ha,
|
|
296
|
+
"hmargin": 5,
|
|
297
|
+
"bbox": {
|
|
298
|
+
"facecolor": (1, 1, 1, 0),
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
with context({"vertex": leaf_vertex_style}):
|
|
303
|
+
leaf_vertex_style = get_style(".vertex")
|
|
304
|
+
self._leaf_vertices = VertexCollection(
|
|
305
|
+
layout=leaf_layout,
|
|
306
|
+
layout_coordinate_system=self._ipx_internal_data.get(
|
|
307
|
+
"layout_coordinate_system",
|
|
308
|
+
"catesian",
|
|
309
|
+
),
|
|
310
|
+
style=leaf_vertex_style,
|
|
311
|
+
labels=self._get_label_series("leaf"),
|
|
312
|
+
transform=self.get_transform(),
|
|
313
|
+
offset_transform=self.get_offset_transform(),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def _add_cascades(self) -> None:
|
|
317
|
+
"""Add cascade patches."""
|
|
318
|
+
# NOTE: If leaf labels are present and the cascades are requested to wrap around them,
|
|
319
|
+
# we have to compute the max extend of the cascades from the leaf labels.
|
|
320
|
+
maxdepth = None
|
|
321
|
+
style_cascade = self.get_vertices().get_style()["cascade"]
|
|
322
|
+
extend_to_labels = style_cascade.get("extend", False) == "leaf_labels"
|
|
323
|
+
has_leaf_labels = self.get_leaf_labels() is not None
|
|
324
|
+
if extend_to_labels and not has_leaf_labels:
|
|
325
|
+
raise ValueError("Cannot extend cascades: no leaf labels.")
|
|
326
|
+
|
|
327
|
+
if extend_to_labels and has_leaf_labels:
|
|
328
|
+
maxdepth = self._get_maxdepth_leaf_labels()
|
|
329
|
+
|
|
330
|
+
self._cascades = CascadeCollection(
|
|
331
|
+
tree=self.tree,
|
|
332
|
+
layout=self.get_layout(),
|
|
333
|
+
layout_name=self._ipx_internal_data["layout_name"],
|
|
334
|
+
orientation=self._ipx_internal_data["orientation"],
|
|
335
|
+
style=style_cascade,
|
|
336
|
+
provider=data_providers["tree"][self._ipx_internal_data["tree_library"]],
|
|
337
|
+
transform=self.get_offset_transform(),
|
|
338
|
+
maxdepth=maxdepth,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
def _get_maxdepth_leaf_labels(self):
|
|
342
|
+
layout_name = self.get_layout_name()
|
|
343
|
+
if layout_name == "radial":
|
|
344
|
+
maxdepth = 0
|
|
345
|
+
# These are the text boxes, they must all be included
|
|
346
|
+
bboxes = self.get_leaf_labels().get_datalims_children(
|
|
347
|
+
self.get_offset_transform()
|
|
348
|
+
)
|
|
349
|
+
for bbox in bboxes:
|
|
350
|
+
r1 = np.linalg.norm([bbox.xmax, bbox.ymax])
|
|
351
|
+
r2 = np.linalg.norm([bbox.xmax, bbox.ymin])
|
|
352
|
+
r3 = np.linalg.norm([bbox.xmin, bbox.ymax])
|
|
353
|
+
r4 = np.linalg.norm([bbox.xmin, bbox.ymin])
|
|
354
|
+
maxdepth = max(maxdepth, r1, r2, r3, r4)
|
|
355
|
+
else:
|
|
356
|
+
orientation = self.get_orientation()
|
|
357
|
+
bbox = self.get_leaf_labels().get_datalim(self.get_offset_transform())
|
|
358
|
+
if (layout_name, orientation) == ("horizontal", "right"):
|
|
359
|
+
maxdepth = bbox.xmax
|
|
360
|
+
elif layout_name == "horizontal":
|
|
361
|
+
maxdepth = bbox.xmin
|
|
362
|
+
elif (layout_name, orientation) == ("vertical", "descending"):
|
|
363
|
+
maxdepth = bbox.ymin
|
|
364
|
+
elif layout_name == "vertical":
|
|
365
|
+
maxdepth = bbox.ymax
|
|
366
|
+
|
|
367
|
+
return maxdepth
|
|
368
|
+
|
|
203
369
|
def _add_edges(self) -> None:
|
|
204
370
|
"""Add edges to the network artist.
|
|
205
371
|
|
|
@@ -259,7 +425,7 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
259
425
|
if layout_name == "horizontal":
|
|
260
426
|
waypointsi = "x0y1"
|
|
261
427
|
elif layout_name == "vertical":
|
|
262
|
-
waypointsi = "
|
|
428
|
+
waypointsi = "y0x1"
|
|
263
429
|
elif layout_name == "radial":
|
|
264
430
|
waypointsi = "r0a1"
|
|
265
431
|
else:
|
|
@@ -296,17 +462,32 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
296
462
|
if "cmap" in edge_style:
|
|
297
463
|
self._edges.set_array(colorarray)
|
|
298
464
|
|
|
465
|
+
def get_layout_name(self) -> str:
|
|
466
|
+
"""Get the layout name."""
|
|
467
|
+
return self._ipx_internal_data["layout_name"]
|
|
468
|
+
|
|
469
|
+
def get_orientation(self) -> Optional[str]:
|
|
470
|
+
"""Get the orientation of the tree layout."""
|
|
471
|
+
return self._ipx_internal_data.get("orientation", None)
|
|
472
|
+
|
|
299
473
|
@_stale_wrapper
|
|
300
474
|
def draw(self, renderer) -> None:
|
|
301
475
|
"""Draw each of the children, with some buffering mechanism."""
|
|
302
476
|
if not self.get_visible():
|
|
303
477
|
return
|
|
304
478
|
|
|
305
|
-
#
|
|
479
|
+
# At the end, if there are cadcades with extent depending on
|
|
480
|
+
# leaf edges, we should update them
|
|
481
|
+
self._update_cascades_extent()
|
|
306
482
|
|
|
307
483
|
# NOTE: looks like we have to manage the zorder ourselves
|
|
308
|
-
# this is kind of funny actually
|
|
484
|
+
# this is kind of funny actually. Btw we need to ensure
|
|
485
|
+
# that cascades are drawn behind (earlier than) vertices
|
|
486
|
+
# and edges at equal zorder because it looks better that way.
|
|
487
|
+
z_suborder = defaultdict(int)
|
|
488
|
+
if hasattr(self, "_cascades"):
|
|
489
|
+
z_suborder[self._cascades] = -1
|
|
309
490
|
children = list(self.get_children())
|
|
310
|
-
children.sort(key=lambda x: x.zorder)
|
|
491
|
+
children.sort(key=lambda x: (x.zorder, z_suborder[x]))
|
|
311
492
|
for art in children:
|
|
312
493
|
art.draw(renderer)
|
iplotx/version.py
CHANGED
iplotx/vertex.py
CHANGED
|
@@ -104,6 +104,7 @@ class VertexCollection(PatchCollection):
|
|
|
104
104
|
"""Set the figure for this artist and all children."""
|
|
105
105
|
super().set_figure(fig)
|
|
106
106
|
self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
|
|
107
|
+
self._update_children()
|
|
107
108
|
for child in self.get_children():
|
|
108
109
|
child.set_figure(fig)
|
|
109
110
|
|
|
@@ -209,6 +210,10 @@ class VertexCollection(PatchCollection):
|
|
|
209
210
|
self._update_offsets_from_layout()
|
|
210
211
|
self.stale = True
|
|
211
212
|
|
|
213
|
+
def get_style(self) -> Optional[dict[str, Any]]:
|
|
214
|
+
"""Get the style dictionary for the vertices."""
|
|
215
|
+
return self._style
|
|
216
|
+
|
|
212
217
|
def _init_vertex_patches(self):
|
|
213
218
|
style = self._style or {}
|
|
214
219
|
if "cmap" in style:
|
|
@@ -231,13 +236,11 @@ class VertexCollection(PatchCollection):
|
|
|
231
236
|
patches = []
|
|
232
237
|
sizes = []
|
|
233
238
|
for i, (vid, row) in enumerate(self._layout.iterrows()):
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
239
|
+
stylei = rotate_style(style, index=i, key=vid)
|
|
240
|
+
if stylei.get("size", 20) == "label":
|
|
241
|
+
stylei["size"] = _get_label_width_height(
|
|
237
242
|
str(self._labels[vid]), **style.get("label", {})
|
|
238
243
|
)
|
|
239
|
-
|
|
240
|
-
stylei = rotate_style(style, index=i, key=vid)
|
|
241
244
|
if cmap_fun is not None:
|
|
242
245
|
colorarray.append(style["facecolor"])
|
|
243
246
|
stylei["facecolor"] = cmap_fun(stylei["facecolor"])
|
|
@@ -299,6 +302,30 @@ class VertexCollection(PatchCollection):
|
|
|
299
302
|
if val and hasattr(self, "stale_callback_post"):
|
|
300
303
|
self.stale_callback_post(self)
|
|
301
304
|
|
|
305
|
+
def _update_children(self) -> None:
|
|
306
|
+
"""Update children before drawing and before first render."""
|
|
307
|
+
self._update_labels()
|
|
308
|
+
|
|
309
|
+
def _update_labels(self) -> None:
|
|
310
|
+
"""Update labels before drawing.
|
|
311
|
+
|
|
312
|
+
NOTE: This needs to work in figure coordinates.
|
|
313
|
+
"""
|
|
314
|
+
if not hasattr(self, "_label_collection"):
|
|
315
|
+
return
|
|
316
|
+
|
|
317
|
+
if self.get_layout_coordinate_system() != "polar":
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
transform = self.get_offset_transform()
|
|
321
|
+
trans = transform.transform
|
|
322
|
+
|
|
323
|
+
zero_fig = trans(np.array([0, 0]))
|
|
324
|
+
offsets_fig = trans(self.get_labels().get_offsets())
|
|
325
|
+
doffsets_fig = offsets_fig - zero_fig
|
|
326
|
+
rotations = np.arctan2(doffsets_fig[:, 1], doffsets_fig[:, 0])
|
|
327
|
+
self.get_labels().set_rotations(rotations)
|
|
328
|
+
|
|
302
329
|
@mpl.artist.allow_rasterization
|
|
303
330
|
def draw(self, renderer):
|
|
304
331
|
if not self.get_visible():
|
|
@@ -311,6 +338,9 @@ class VertexCollection(PatchCollection):
|
|
|
311
338
|
|
|
312
339
|
self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
|
|
313
340
|
|
|
341
|
+
# Set the label rotations already, hopefully this is not too early
|
|
342
|
+
self._update_children()
|
|
343
|
+
|
|
314
344
|
# NOTE: This draws the vertices first, then the labels.
|
|
315
345
|
# The correct order would be vertex1->label1->vertex2->label2, etc.
|
|
316
346
|
# We might fix if we manage to find a way to do it.
|
|
@@ -320,10 +350,12 @@ class VertexCollection(PatchCollection):
|
|
|
320
350
|
|
|
321
351
|
|
|
322
352
|
def make_patch(
|
|
323
|
-
marker: str
|
|
353
|
+
marker: str = "o",
|
|
354
|
+
size: float | Sequence[float] = 20,
|
|
355
|
+
**kwargs,
|
|
324
356
|
) -> tuple[Patch, float]:
|
|
325
357
|
"""Make a patch of the given marker shape and size."""
|
|
326
|
-
forbidden_props = ["label", "cmap", "norm"]
|
|
358
|
+
forbidden_props = ["label", "cmap", "norm", "cascade"]
|
|
327
359
|
for prop in forbidden_props:
|
|
328
360
|
if prop in kwargs:
|
|
329
361
|
kwargs.pop(prop)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iplotx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Plot networkx from igraph and networkx.
|
|
5
5
|
Project-URL: Homepage, https://github.com/fabilab/iplotx
|
|
6
6
|
Project-URL: Documentation, https://readthedocs.org/iplotx
|
|
@@ -79,6 +79,7 @@ See [gallery](https://iplotx.readthedocs.io/en/latest/gallery/index.html).
|
|
|
79
79
|
- Efficient plotting of large graphs using matplotlib's collection functionality. ✅
|
|
80
80
|
- Support editing plotting elements after the plot is created, e.g. changing node colors, labels, etc. ✅
|
|
81
81
|
- Support animations, e.g. showing the evolution of a network over time. ✅
|
|
82
|
+
- Support mouse interaction, e.g. hovering over or clicking on nodes and edges to get information about them. ✅
|
|
82
83
|
- Support trees from special libraries such as ete3, biopython, etc. This will need a dedicated function and layouting. ✅
|
|
83
84
|
- Support uni- and bi-directional communication between graph object and plot object.🏗️
|
|
84
85
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
iplotx/__init__.py,sha256=DIOWUaEo9vgmz8B6hBlurV95BEYmjmUe5BU-jRWrtG4,418
|
|
2
|
+
iplotx/cascades.py,sha256=_J8kxCHJrZmPKaN10e0uROAPsdcLrw41hzozZu1hQcg,8405
|
|
3
|
+
iplotx/groups.py,sha256=H3zIaqlILQaC6Iqgxrf3zSuRVgynQTLLEuuGy6lYgLI,6388
|
|
4
|
+
iplotx/label.py,sha256=m2ryXMh29QFLwdstRphnOPocW_Wnqir9WQscxWN1Z2E,7347
|
|
5
|
+
iplotx/layout.py,sha256=0Mqrs0odQ8Jk4nITvAYgayR6KDm3e-Lq1BY_DKeU3ok,4664
|
|
6
|
+
iplotx/network.py,sha256=rLVfC52PBiTR0I-CCvFPE0dj7t-KCLZwXSSJrpPVzh8,9719
|
|
7
|
+
iplotx/plotting.py,sha256=VU1LYp9peqLa1CmvHjF98BYxezkWUrcCWvd2MPB9n_8,7381
|
|
8
|
+
iplotx/style.py,sha256=NtrKYhTIkf7GPpepsROUbXxbodIc8gu066CdLNAsMQM,12009
|
|
9
|
+
iplotx/tree.py,sha256=YU3UoGV_Zxf_KkP4iIsi09WsL1vBvS3URkG-Uqzqyp4,18393
|
|
10
|
+
iplotx/typing.py,sha256=17TS-EJshNNOdeUC9mmJALiJ3gwI1nRq6AGlVdQRp1E,1384
|
|
11
|
+
iplotx/version.py,sha256=YK2iFN0pBkof4-yJ6Yzz65Uy4liSHJ30ixvRZVehhho,66
|
|
12
|
+
iplotx/vertex.py,sha256=JCXv-l934jDGdS2fOigOdDKDZ-5Az-7MUqHcQk8Jl18,12715
|
|
13
|
+
iplotx/edge/__init__.py,sha256=W68WienFUneWQ0iVTFGNm8KIBQGvkfXlYq2Kx1lDzU8,23076
|
|
14
|
+
iplotx/edge/arrow.py,sha256=2mF_HwhHkXir3lOYOTp8HW__PQgWRxaHXZN9mvk3hLQ,11452
|
|
15
|
+
iplotx/edge/geometry.py,sha256=7RwtIUOjz6pd1QTUaNwtIivnaM4ZfxVgMjTg1bIqqQA,12827
|
|
16
|
+
iplotx/edge/ports.py,sha256=pno3A7QdQyZGyQJOHVLYszBBeQAh-q2-jkd-lOe31TM,1183
|
|
17
|
+
iplotx/ingest/__init__.py,sha256=dEbdb3LP3QnPKpwA7y6fJl9spCWkCYYtXCQPCGA9oVk,4971
|
|
18
|
+
iplotx/ingest/heuristics.py,sha256=_ZSC9EiCr-eURmhGdufdZdyERpzPrPvRj01nv0rXkiE,6579
|
|
19
|
+
iplotx/ingest/typing.py,sha256=cPEvOT5yjDiXj6Y9E3ShH2uDmepn7YtPtBIZ_cOEp6g,10345
|
|
20
|
+
iplotx/ingest/providers/network/igraph.py,sha256=lCWIlKd2VwxJLoc2W4nivwkrvFjhmXhG9r_sJkFXWh0,2794
|
|
21
|
+
iplotx/ingest/providers/network/networkx.py,sha256=-lb_8jpq7DvoM9lWn43qL6jTqS-VqtpJEgcSJeFz7yQ,4261
|
|
22
|
+
iplotx/ingest/providers/tree/biopython.py,sha256=JFuD7NWV7j-fWt-6q29ApvlKf7Mqrnr_6wSkzSK-e38,1080
|
|
23
|
+
iplotx/ingest/providers/tree/cogent3.py,sha256=LHAFLronzlrlLWl79abXWOoFP-agIdzoQqj6O15xwLE,873
|
|
24
|
+
iplotx/ingest/providers/tree/ete4.py,sha256=vHwfYRazruR7eZfUgEJiSyeUA7UVBsTtR998uoL5bDQ,971
|
|
25
|
+
iplotx/ingest/providers/tree/skbio.py,sha256=fwlITpl71gIKDDoJKB6nmlC-J6A2fXoplX6ul5qXsYc,869
|
|
26
|
+
iplotx/utils/geometry.py,sha256=K5ZBYPmz4-KNm64pDh2p0L6PF5-u57SCVaEd2eWeRv0,8956
|
|
27
|
+
iplotx/utils/internal.py,sha256=WWfcZDGK8Ut1y_tOHRGg9wSqY1bwSeLQO7dHM_8Tvwo,107
|
|
28
|
+
iplotx/utils/matplotlib.py,sha256=T37SMwKNSA-dRKgNkVw5H4fGr5NACtxvBPebDJGdhjk,4516
|
|
29
|
+
iplotx/utils/style.py,sha256=fEi14nc37HQqHxZTPeQaTnFFwbSneY1oDCyv2UbWfKk,22
|
|
30
|
+
iplotx-0.3.0.dist-info/METADATA,sha256=7wB_lF97KjI_w1UAySphid4gt1bRCr5gGZ0YoPMqBW0,3809
|
|
31
|
+
iplotx-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
32
|
+
iplotx-0.3.0.dist-info/RECORD,,
|
iplotx-0.2.1.dist-info/RECORD
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
iplotx/__init__.py,sha256=DIOWUaEo9vgmz8B6hBlurV95BEYmjmUe5BU-jRWrtG4,418
|
|
2
|
-
iplotx/groups.py,sha256=H3zIaqlILQaC6Iqgxrf3zSuRVgynQTLLEuuGy6lYgLI,6388
|
|
3
|
-
iplotx/label.py,sha256=j-OjOX1R7VHh3Ihg4bhHaauxS4fuVHhGcHKoETAnUt4,5308
|
|
4
|
-
iplotx/layout.py,sha256=RLCDgG9eO3mtZH-H3ETJFpvPR0uVl-xMUsgC4mcepWE,3930
|
|
5
|
-
iplotx/network.py,sha256=rLVfC52PBiTR0I-CCvFPE0dj7t-KCLZwXSSJrpPVzh8,9719
|
|
6
|
-
iplotx/plotting.py,sha256=1C6wenDeUohoYVl_vAcTs2aR-5LcRHFOcNb4ghIbkLI,7152
|
|
7
|
-
iplotx/style.py,sha256=CmtycJKndmh_w28s4SpBJECyaAn8pQkfP244dMsAvv0,11399
|
|
8
|
-
iplotx/tree.py,sha256=pu3dyIPsdX4iPJOSk_J5-IPajkCBkmUhEhp0jXzurtw,10695
|
|
9
|
-
iplotx/typing.py,sha256=17TS-EJshNNOdeUC9mmJALiJ3gwI1nRq6AGlVdQRp1E,1384
|
|
10
|
-
iplotx/version.py,sha256=RKuLXcXQo3HLVM55AR--Vu2Gmk8BKgdvxPEqTMqIa1E,66
|
|
11
|
-
iplotx/vertex.py,sha256=q5B2_JUsGGszUh6OJ_ddr6uBvNmrxigolI0d5C-MThc,11667
|
|
12
|
-
iplotx/edge/__init__.py,sha256=uM7YTr7YDbLIhAjUjfF7xT52LuE2CMj-erPeq68roxI,22227
|
|
13
|
-
iplotx/edge/arrow.py,sha256=2mF_HwhHkXir3lOYOTp8HW__PQgWRxaHXZN9mvk3hLQ,11452
|
|
14
|
-
iplotx/edge/geometry.py,sha256=db-k6HFYb_h6rWWxaD32tKefvRUIb7DLi1Y2x5vhgMA,10849
|
|
15
|
-
iplotx/edge/ports.py,sha256=pno3A7QdQyZGyQJOHVLYszBBeQAh-q2-jkd-lOe31TM,1183
|
|
16
|
-
iplotx/ingest/__init__.py,sha256=75Pml7X65tP8b2G3qaeZUdnDgwP6dclcCEEFl0BYSdo,4707
|
|
17
|
-
iplotx/ingest/heuristics.py,sha256=32AZ8iidM_uooaBKe2EMjNU_nJDbhA_28ADc0dpQy5A,6636
|
|
18
|
-
iplotx/ingest/typing.py,sha256=QEgCpLyfp-0v9czn6OaJ0_nuCo1AXv3lGS3aD6w-Ezw,3134
|
|
19
|
-
iplotx/ingest/providers/network/igraph.py,sha256=_J7lH-jrT0_1oSfwgT_mQMRhxNoFwHo8dyBLCbgqETQ,2766
|
|
20
|
-
iplotx/ingest/providers/network/networkx.py,sha256=u7NegapWZ0gWUj5n1PUVD-zZ92lKUiv6BLNTNIrXlRk,4233
|
|
21
|
-
iplotx/ingest/providers/tree/biopython.py,sha256=7ZVD_WwIaBOSl-at4r_Y4d2qHQq6BcvlV-yDxMVCWnw,3456
|
|
22
|
-
iplotx/ingest/providers/tree/cogent3.py,sha256=5O92zkdA43LWQ7h6r-uG_5X76EPHo-Yx9ODLb0hd_qc,3636
|
|
23
|
-
iplotx/ingest/providers/tree/ete4.py,sha256=sGm2363yLJWsWsrRn2iFx8qRiq57h_5dQWBD41xJmhY,3660
|
|
24
|
-
iplotx/ingest/providers/tree/skbio.py,sha256=iNZhY_TNLpzd55cK2nybjiftUfakeOrDDJ9dMBna_io,3629
|
|
25
|
-
iplotx/utils/geometry.py,sha256=K5ZBYPmz4-KNm64pDh2p0L6PF5-u57SCVaEd2eWeRv0,8956
|
|
26
|
-
iplotx/utils/internal.py,sha256=WWfcZDGK8Ut1y_tOHRGg9wSqY1bwSeLQO7dHM_8Tvwo,107
|
|
27
|
-
iplotx/utils/matplotlib.py,sha256=T37SMwKNSA-dRKgNkVw5H4fGr5NACtxvBPebDJGdhjk,4516
|
|
28
|
-
iplotx/utils/style.py,sha256=fEi14nc37HQqHxZTPeQaTnFFwbSneY1oDCyv2UbWfKk,22
|
|
29
|
-
iplotx-0.2.1.dist-info/METADATA,sha256=_xHHXy4GbmnU5phRruUTbr1XlBtCJnOOyq5PLgv-m1k,3695
|
|
30
|
-
iplotx-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
31
|
-
iplotx-0.2.1.dist-info/RECORD,,
|
|
File without changes
|