iplotx 0.5.1__py3-none-any.whl → 0.5.2.dev0__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 +20 -6
- iplotx/edge/arrow.py +79 -1
- iplotx/edge/geometry.py +91 -42
- iplotx/ingest/providers/network/simple.py +4 -0
- iplotx/ingest/providers/tree/simple.py +2 -0
- iplotx/ingest/typing.py +10 -8
- iplotx/label.py +6 -0
- iplotx/plotting.py +1 -2
- iplotx/style/__init__.py +1 -1
- iplotx/style/leaf_info.py +2 -0
- iplotx/style/library.py +22 -0
- iplotx/tree.py +6 -9
- iplotx/utils/matplotlib.py +3 -0
- iplotx/version.py +1 -1
- {iplotx-0.5.1.dist-info → iplotx-0.5.2.dev0.dist-info}/METADATA +1 -1
- {iplotx-0.5.1.dist-info → iplotx-0.5.2.dev0.dist-info}/RECORD +17 -17
- {iplotx-0.5.1.dist-info → iplotx-0.5.2.dev0.dist-info}/WHEEL +0 -0
iplotx/edge/__init__.py
CHANGED
|
@@ -105,6 +105,10 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
105
105
|
# NOTE: This should also set the transform
|
|
106
106
|
super().__init__(patches, transform=transform, *args, **kwargs)
|
|
107
107
|
|
|
108
|
+
# Apparenyly capstyle is lost upon collection creation
|
|
109
|
+
if "capstyle" in self._style:
|
|
110
|
+
self.set_capstyle(self._style["capstyle"])
|
|
111
|
+
|
|
108
112
|
# This is important because it prepares the right flags for scalarmappable
|
|
109
113
|
self.set_facecolor("none")
|
|
110
114
|
|
|
@@ -158,6 +162,10 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
158
162
|
**kwargs,
|
|
159
163
|
)
|
|
160
164
|
|
|
165
|
+
# Apparently capstyle is lost upon collection creation
|
|
166
|
+
if "capstyle" in style:
|
|
167
|
+
self._subedges.set_capstyle(style["capstyle"])
|
|
168
|
+
|
|
161
169
|
def get_children(self) -> tuple:
|
|
162
170
|
children = []
|
|
163
171
|
if hasattr(self, "_subedges"):
|
|
@@ -188,7 +196,7 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
188
196
|
return self._directed
|
|
189
197
|
|
|
190
198
|
@directed.setter
|
|
191
|
-
def directed(self, value) -> None:
|
|
199
|
+
def directed(self, value: bool) -> None:
|
|
192
200
|
"""Setter for the directed property.
|
|
193
201
|
|
|
194
202
|
Changing this property triggers the addition/removal of arrows from the plot.
|
|
@@ -230,7 +238,11 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
230
238
|
# NOTE: The superclass also sets stale = True
|
|
231
239
|
super().update_scalarmappable()
|
|
232
240
|
# Now self._edgecolors has the correct colorspace values
|
|
233
|
-
|
|
241
|
+
# NOTE: The following line should include a condition on
|
|
242
|
+
# whether the arrows are allowing color matching to the
|
|
243
|
+
# edges. For now, we assume that if the edges are colormapped
|
|
244
|
+
# we would want the arrows to be as well.
|
|
245
|
+
if hasattr(self, "_arrows") and (self._A is not None):
|
|
234
246
|
self._arrows.set_colors(self.get_edgecolor())
|
|
235
247
|
|
|
236
248
|
def get_labels(self) -> Optional[LabelCollection]:
|
|
@@ -337,6 +349,10 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
337
349
|
tension = 0
|
|
338
350
|
ports = None
|
|
339
351
|
|
|
352
|
+
# Scale padding by dpi
|
|
353
|
+
dpi = self.figure.dpi if hasattr(self, "figure") else 72.0
|
|
354
|
+
padding = dpi / 72.0 * edge_stylei.pop("padding", 0)
|
|
355
|
+
|
|
340
356
|
# False is a synonym for "none"
|
|
341
357
|
waypoints = edge_stylei.get("waypoints", "none")
|
|
342
358
|
if waypoints is False or waypoints is np.False_:
|
|
@@ -348,9 +364,6 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
348
364
|
if waypoints != "none":
|
|
349
365
|
ports = edge_stylei.get("ports", (None, None))
|
|
350
366
|
|
|
351
|
-
if not isinstance(waypoints, str):
|
|
352
|
-
__import__("ipdb").set_trace()
|
|
353
|
-
|
|
354
367
|
# Compute actual edge path
|
|
355
368
|
path, angles = _compute_edge_path(
|
|
356
369
|
vcoord_data,
|
|
@@ -362,6 +375,7 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
362
375
|
waypoints=waypoints,
|
|
363
376
|
ports=ports,
|
|
364
377
|
layout_coordinate_system=self._vertex_collection.get_layout_coordinate_system(),
|
|
378
|
+
padding=padding,
|
|
365
379
|
)
|
|
366
380
|
|
|
367
381
|
offset = edge_stylei.get("offset", 0)
|
|
@@ -375,7 +389,6 @@ class EdgeCollection(mpl.collections.PatchCollection):
|
|
|
375
389
|
offset = offset * vrot
|
|
376
390
|
offset = np.asarray(offset, dtype=float)
|
|
377
391
|
# Scale by dpi
|
|
378
|
-
dpi = self.figure.dpi if hasattr(self, "figure") else 72.0
|
|
379
392
|
offset *= dpi / 72.0
|
|
380
393
|
if (offset != 0).any():
|
|
381
394
|
path.vertices[:] = trans_inv(trans(path.vertices) + offset)
|
|
@@ -702,6 +715,7 @@ def make_stub_patch(**kwargs):
|
|
|
702
715
|
"cmap",
|
|
703
716
|
"norm",
|
|
704
717
|
"split",
|
|
718
|
+
"padding",
|
|
705
719
|
]
|
|
706
720
|
for prop in forbidden_props:
|
|
707
721
|
if prop in kwargs:
|
iplotx/edge/arrow.py
CHANGED
|
@@ -220,6 +220,16 @@ def make_arrow_patch(marker: str = "|>", width: float = 8, **kwargs):
|
|
|
220
220
|
codes=[getattr(mpl.path.Path, x) for x in codes],
|
|
221
221
|
closed=False,
|
|
222
222
|
)
|
|
223
|
+
elif marker == "<":
|
|
224
|
+
kwargs["facecolor"] = "none"
|
|
225
|
+
if "color" in kwargs:
|
|
226
|
+
kwargs["edgecolor"] = kwargs.pop("color")
|
|
227
|
+
codes = ["MOVETO", "LINETO", "LINETO"]
|
|
228
|
+
path = mpl.path.Path(
|
|
229
|
+
np.array([[height, width * 0.5], [0, 0], [height, -width * 0.5]]),
|
|
230
|
+
codes=[getattr(mpl.path.Path, x) for x in codes],
|
|
231
|
+
closed=False,
|
|
232
|
+
)
|
|
223
233
|
elif marker == ">>":
|
|
224
234
|
if "color" in kwargs:
|
|
225
235
|
kwargs["facecolor"] = kwargs["edgecolor"] = kwargs.pop("color")
|
|
@@ -272,13 +282,80 @@ def make_arrow_patch(marker: str = "|>", width: float = 8, **kwargs):
|
|
|
272
282
|
codes=[getattr(mpl.path.Path, x) for x in codes],
|
|
273
283
|
closed=False,
|
|
274
284
|
)
|
|
285
|
+
elif marker == "(":
|
|
286
|
+
kwargs["facecolor"] = "none"
|
|
287
|
+
if "color" in kwargs:
|
|
288
|
+
kwargs["edgecolor"] = kwargs.pop("color")
|
|
289
|
+
codes = ["MOVETO", "CURVE3", "CURVE3"]
|
|
290
|
+
path = mpl.path.Path(
|
|
291
|
+
np.array(
|
|
292
|
+
[
|
|
293
|
+
[height * 0.5, width * 0.5],
|
|
294
|
+
[-height * 0.5, 0],
|
|
295
|
+
[height * 0.5, -width * 0.5],
|
|
296
|
+
]
|
|
297
|
+
),
|
|
298
|
+
codes=[getattr(mpl.path.Path, x) for x in codes],
|
|
299
|
+
closed=False,
|
|
300
|
+
)
|
|
301
|
+
elif marker == "]":
|
|
302
|
+
kwargs["facecolor"] = "none"
|
|
303
|
+
if "color" in kwargs:
|
|
304
|
+
kwargs["edgecolor"] = kwargs.pop("color")
|
|
305
|
+
codes = ["MOVETO", "LINETO", "LINETO", "LINETO"]
|
|
306
|
+
path = mpl.path.Path(
|
|
307
|
+
np.array(
|
|
308
|
+
[
|
|
309
|
+
[-height, width * 0.5],
|
|
310
|
+
[0, width * 0.5],
|
|
311
|
+
[0, -width * 0.5],
|
|
312
|
+
[-height, -width * 0.5],
|
|
313
|
+
]
|
|
314
|
+
),
|
|
315
|
+
codes=[getattr(mpl.path.Path, x) for x in codes],
|
|
316
|
+
closed=False,
|
|
317
|
+
)
|
|
318
|
+
elif marker == "[":
|
|
319
|
+
kwargs["facecolor"] = "none"
|
|
320
|
+
if "color" in kwargs:
|
|
321
|
+
kwargs["edgecolor"] = kwargs.pop("color")
|
|
322
|
+
codes = ["MOVETO", "LINETO", "LINETO", "LINETO"]
|
|
323
|
+
path = mpl.path.Path(
|
|
324
|
+
np.array(
|
|
325
|
+
[
|
|
326
|
+
[height, width * 0.5],
|
|
327
|
+
[0, width * 0.5],
|
|
328
|
+
[0, -width * 0.5],
|
|
329
|
+
[height, -width * 0.5],
|
|
330
|
+
]
|
|
331
|
+
),
|
|
332
|
+
codes=[getattr(mpl.path.Path, x) for x in codes],
|
|
333
|
+
closed=False,
|
|
334
|
+
)
|
|
275
335
|
elif marker == "|":
|
|
276
336
|
kwargs["facecolor"] = "none"
|
|
277
337
|
if "color" in kwargs:
|
|
278
338
|
kwargs["edgecolor"] = kwargs.pop("color")
|
|
279
339
|
codes = ["MOVETO", "LINETO"]
|
|
280
340
|
path = mpl.path.Path(
|
|
281
|
-
np.array([[
|
|
341
|
+
np.array([[0, width * 0.5], [0, -width * 0.5]]),
|
|
342
|
+
codes=[getattr(mpl.path.Path, x) for x in codes],
|
|
343
|
+
closed=False,
|
|
344
|
+
)
|
|
345
|
+
elif marker in ("x", "X"):
|
|
346
|
+
kwargs["facecolor"] = "none"
|
|
347
|
+
if "color" in kwargs:
|
|
348
|
+
kwargs["edgecolor"] = kwargs.pop("color")
|
|
349
|
+
codes = ["MOVETO", "LINETO", "MOVETO", "LINETO"]
|
|
350
|
+
path = mpl.path.Path(
|
|
351
|
+
np.array(
|
|
352
|
+
[
|
|
353
|
+
[height * 0.5, width * 0.5],
|
|
354
|
+
[-height * 0.5, -width * 0.5],
|
|
355
|
+
[-height * 0.5, width * 0.5],
|
|
356
|
+
[height * 0.5, -width * 0.5],
|
|
357
|
+
]
|
|
358
|
+
),
|
|
282
359
|
codes=[getattr(mpl.path.Path, x) for x in codes],
|
|
283
360
|
closed=False,
|
|
284
361
|
)
|
|
@@ -370,4 +447,5 @@ def make_arrow_patch(marker: str = "|>", width: float = 8, **kwargs):
|
|
|
370
447
|
path,
|
|
371
448
|
**kwargs,
|
|
372
449
|
)
|
|
450
|
+
|
|
373
451
|
return patch, size_max
|
iplotx/edge/geometry.py
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
Support module with geometry- and path-related functions for edges.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import (
|
|
6
|
+
Optional,
|
|
7
|
+
Sequence,
|
|
8
|
+
)
|
|
6
9
|
from math import atan2, tan, pi
|
|
7
10
|
import numpy as np
|
|
8
11
|
import matplotlib as mpl
|
|
@@ -61,45 +64,51 @@ def _compute_loops_per_angle(nloops, angles):
|
|
|
61
64
|
]
|
|
62
65
|
|
|
63
66
|
|
|
64
|
-
def _get_shorter_edge_coords(vpath, vsize, theta):
|
|
67
|
+
def _get_shorter_edge_coords(vpath, vsize, theta, padding=0):
|
|
65
68
|
# Bound theta from -pi to pi (why is that not guaranteed?)
|
|
66
69
|
theta = (theta + pi) % (2 * pi) - pi
|
|
67
70
|
|
|
68
71
|
# Size zero vertices need no shortening
|
|
69
72
|
if vsize == 0:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
for i in range(len(vpath)):
|
|
73
|
-
v1 = vpath.vertices[i]
|
|
74
|
-
v2 = vpath.vertices[(i + 1) % len(vpath)]
|
|
75
|
-
theta1 = atan2(*((v1)[::-1]))
|
|
76
|
-
theta2 = atan2(*((v2)[::-1]))
|
|
77
|
-
|
|
78
|
-
# atan2 ranges ]-3.14, 3.14]
|
|
79
|
-
# so it can be that theta1 is -3 and theta2 is +3
|
|
80
|
-
# therefore we need two separate cases, one that cuts at pi and one at 0
|
|
81
|
-
cond1 = theta1 <= theta <= theta2
|
|
82
|
-
cond2 = (
|
|
83
|
-
(theta1 + 2 * pi) % (2 * pi)
|
|
84
|
-
<= (theta + 2 * pi) % (2 * pi)
|
|
85
|
-
<= (theta2 + 2 * pi) % (2 * pi)
|
|
86
|
-
)
|
|
87
|
-
if cond1 or cond2:
|
|
88
|
-
break
|
|
73
|
+
ve = np.array([0, 0])
|
|
89
74
|
else:
|
|
90
|
-
|
|
75
|
+
for i in range(len(vpath)):
|
|
76
|
+
v1 = vpath.vertices[i]
|
|
77
|
+
v2 = vpath.vertices[(i + 1) % len(vpath)]
|
|
78
|
+
theta1 = atan2(*((v1)[::-1]))
|
|
79
|
+
theta2 = atan2(*((v2)[::-1]))
|
|
80
|
+
|
|
81
|
+
# atan2 ranges ]-3.14, 3.14]
|
|
82
|
+
# so it can be that theta1 is -3 and theta2 is +3
|
|
83
|
+
# therefore we need two separate cases, one that cuts at pi and one at 0
|
|
84
|
+
cond1 = theta1 <= theta <= theta2
|
|
85
|
+
cond2 = (
|
|
86
|
+
(theta1 + 2 * pi) % (2 * pi)
|
|
87
|
+
<= (theta + 2 * pi) % (2 * pi)
|
|
88
|
+
<= (theta2 + 2 * pi) % (2 * pi)
|
|
89
|
+
)
|
|
90
|
+
if cond1 or cond2:
|
|
91
|
+
break
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError("Angle for patch not found")
|
|
91
94
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
# The edge meets the patch of the vertex on the v1-v2 size,
|
|
96
|
+
# at angle theta from the center
|
|
97
|
+
mtheta = tan(theta)
|
|
98
|
+
if v2[0] == v1[0]:
|
|
99
|
+
xe = v1[0]
|
|
100
|
+
else:
|
|
101
|
+
m12 = (v2[1] - v1[1]) / (v2[0] - v1[0])
|
|
102
|
+
xe = (v1[1] - m12 * v1[0]) / (mtheta - m12)
|
|
103
|
+
ye = mtheta * xe
|
|
104
|
+
ve = np.array([xe, ye])
|
|
105
|
+
|
|
106
|
+
ve = ve * vsize
|
|
107
|
+
|
|
108
|
+
# Padding (assuming dpi scaling is already applied to the padding)
|
|
109
|
+
ve += padding * np.array([np.cos(theta), np.sin(theta)])
|
|
110
|
+
|
|
111
|
+
return ve
|
|
103
112
|
|
|
104
113
|
|
|
105
114
|
def _fix_parallel_edges_straight(
|
|
@@ -136,11 +145,12 @@ def _compute_loop_path(
|
|
|
136
145
|
angle2,
|
|
137
146
|
trans_inv,
|
|
138
147
|
looptension,
|
|
148
|
+
padding=0,
|
|
139
149
|
):
|
|
140
150
|
# Shorten at starting angle
|
|
141
|
-
start = _get_shorter_edge_coords(vpath, vsize, angle1) + vcoord_fig
|
|
151
|
+
start = _get_shorter_edge_coords(vpath, vsize, angle1, padding) + vcoord_fig
|
|
142
152
|
# Shorten at end angle
|
|
143
|
-
end = _get_shorter_edge_coords(vpath, vsize, angle2) + vcoord_fig
|
|
153
|
+
end = _get_shorter_edge_coords(vpath, vsize, angle2, padding) + vcoord_fig
|
|
144
154
|
|
|
145
155
|
aux1 = (start - vcoord_fig) * looptension + vcoord_fig
|
|
146
156
|
aux2 = (end - vcoord_fig) * looptension + vcoord_fig
|
|
@@ -172,6 +182,7 @@ def _compute_edge_path_straight(
|
|
|
172
182
|
trans,
|
|
173
183
|
trans_inv,
|
|
174
184
|
layout_coordinate_system: str = "cartesian",
|
|
185
|
+
padding: float = 0,
|
|
175
186
|
**kwargs,
|
|
176
187
|
):
|
|
177
188
|
if layout_coordinate_system not in ("cartesian", "polar"):
|
|
@@ -200,11 +211,11 @@ def _compute_edge_path_straight(
|
|
|
200
211
|
theta = atan2(*((vcoord_fig[1] - vcoord_fig[0])[::-1]))
|
|
201
212
|
|
|
202
213
|
# Shorten at starting vertex
|
|
203
|
-
vs = _get_shorter_edge_coords(vpath_fig[0], vsize_fig[0], theta) + vcoord_fig[0]
|
|
214
|
+
vs = _get_shorter_edge_coords(vpath_fig[0], vsize_fig[0], theta, padding) + vcoord_fig[0]
|
|
204
215
|
points.append(vs)
|
|
205
216
|
|
|
206
217
|
# Shorten at end vertex
|
|
207
|
-
ve = _get_shorter_edge_coords(vpath_fig[1], vsize_fig[1], theta + pi) + vcoord_fig[1]
|
|
218
|
+
ve = _get_shorter_edge_coords(vpath_fig[1], vsize_fig[1], theta + pi, padding) + vcoord_fig[1]
|
|
208
219
|
points.append(ve)
|
|
209
220
|
|
|
210
221
|
codes = ["MOVETO", "LINETO"]
|
|
@@ -226,9 +237,41 @@ def _compute_edge_path_waypoints(
|
|
|
226
237
|
layout_coordinate_system: str = "cartesian",
|
|
227
238
|
points_per_curve: int = 30,
|
|
228
239
|
ports: Pair[Optional[str]] = (None, None),
|
|
240
|
+
padding: float = 0,
|
|
229
241
|
**kwargs,
|
|
230
242
|
):
|
|
231
|
-
if
|
|
243
|
+
if not isinstance(waypoints, str):
|
|
244
|
+
# Only cartesian coordinates supported for numerical waypoints for now
|
|
245
|
+
assert layout_coordinate_system == "cartesian"
|
|
246
|
+
|
|
247
|
+
waypoints = trans(np.array(waypoints, ndmin=2))
|
|
248
|
+
|
|
249
|
+
# Coordinates in figure (default) coords
|
|
250
|
+
vcoord_fig = trans(vcoord_data)
|
|
251
|
+
|
|
252
|
+
# Angles of the straight lines
|
|
253
|
+
thetas = [None, None]
|
|
254
|
+
vshorts = [None, None]
|
|
255
|
+
for i in range(2):
|
|
256
|
+
# This picks always the first waypoint for i == 0,
|
|
257
|
+
# the last waypoint for i == 1. They might be the same.
|
|
258
|
+
waypoint = waypoints[-i]
|
|
259
|
+
if ports[i] is None:
|
|
260
|
+
thetas[i] = atan2(*((waypoint - vcoord_fig[i])[::-1]))
|
|
261
|
+
else:
|
|
262
|
+
thetas[i] = atan2(*(_get_port_unit_vector(ports[i], trans_inv)[::-1]))
|
|
263
|
+
|
|
264
|
+
# Shorten at vertex border
|
|
265
|
+
vshorts[i] = (
|
|
266
|
+
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i], padding)
|
|
267
|
+
+ vcoord_fig[i]
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
points = [vshorts[0]] + list(waypoints) + [vshorts[1]]
|
|
271
|
+
codes = ["MOVETO"] + ["LINETO"] * len(waypoints) + ["LINETO"]
|
|
272
|
+
angles = tuple(thetas)
|
|
273
|
+
|
|
274
|
+
elif waypoints in ("x0y1", "y0x1"):
|
|
232
275
|
assert layout_coordinate_system == "cartesian"
|
|
233
276
|
|
|
234
277
|
# Coordinates in figure (default) coords
|
|
@@ -250,7 +293,8 @@ def _compute_edge_path_waypoints(
|
|
|
250
293
|
|
|
251
294
|
# Shorten at vertex border
|
|
252
295
|
vshorts[i] = (
|
|
253
|
-
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i])
|
|
296
|
+
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i], padding)
|
|
297
|
+
+ vcoord_fig[i]
|
|
254
298
|
)
|
|
255
299
|
|
|
256
300
|
# Shorten waypoints to keep the angles right
|
|
@@ -298,7 +342,9 @@ def _compute_edge_path_waypoints(
|
|
|
298
342
|
theta = atan2(*(_get_port_unit_vector(ports[i], trans_inv)[::-1]))
|
|
299
343
|
|
|
300
344
|
# Shorten at vertex border
|
|
301
|
-
vshort =
|
|
345
|
+
vshort = (
|
|
346
|
+
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], theta, padding) + vcoord_fig[i]
|
|
347
|
+
)
|
|
302
348
|
thetas.append(theta)
|
|
303
349
|
vshorts.append(vshort)
|
|
304
350
|
|
|
@@ -345,6 +391,7 @@ def _compute_edge_path_curved(
|
|
|
345
391
|
trans,
|
|
346
392
|
trans_inv,
|
|
347
393
|
ports: Pair[Optional[str]] = (None, None),
|
|
394
|
+
padding: float = 0,
|
|
348
395
|
):
|
|
349
396
|
"""Shorten the edge path along a cubic Bezier between the vertex centres.
|
|
350
397
|
|
|
@@ -397,7 +444,9 @@ def _compute_edge_path_curved(
|
|
|
397
444
|
thetas = [None, None]
|
|
398
445
|
for i in range(2):
|
|
399
446
|
thetas[i] = atan2(*((auxs[i] - vcoord_fig[i])[::-1]))
|
|
400
|
-
vs[i] =
|
|
447
|
+
vs[i] = (
|
|
448
|
+
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i], padding) + vcoord_fig[i]
|
|
449
|
+
)
|
|
401
450
|
|
|
402
451
|
path = {
|
|
403
452
|
"vertices": [
|
|
@@ -422,7 +471,7 @@ def _compute_edge_path_curved(
|
|
|
422
471
|
def _compute_edge_path(
|
|
423
472
|
*args,
|
|
424
473
|
tension: float = 0,
|
|
425
|
-
waypoints: str = "none",
|
|
474
|
+
waypoints: str | tuple[float, float] | Sequence[tuple[float, float]] | np.ndarray = "none",
|
|
426
475
|
ports: Pair[Optional[str]] = (None, None),
|
|
427
476
|
layout_coordinate_system: str = "cartesian",
|
|
428
477
|
**kwargs,
|
|
@@ -79,6 +79,10 @@ class SimpleNetworkDataProvider(NetworkDataProvider):
|
|
|
79
79
|
edge_df = pd.DataFrame(columns=["_ipx_source", "_ipx_target"])
|
|
80
80
|
del tmp
|
|
81
81
|
|
|
82
|
+
# Edge labels
|
|
83
|
+
if edge_labels is not None:
|
|
84
|
+
edge_df["label"] = edge_labels
|
|
85
|
+
|
|
82
86
|
network_data = {
|
|
83
87
|
"vertex_df": vertex_df,
|
|
84
88
|
"edge_df": edge_df,
|
|
@@ -21,6 +21,7 @@ class SimpleTree:
|
|
|
21
21
|
|
|
22
22
|
children: Sequence[Self] = []
|
|
23
23
|
branch_length: float = 1
|
|
24
|
+
name: str = ""
|
|
24
25
|
|
|
25
26
|
@classmethod
|
|
26
27
|
def from_dict(cls, data: dict) -> Self:
|
|
@@ -35,6 +36,7 @@ class SimpleTree:
|
|
|
35
36
|
"""
|
|
36
37
|
tree = cls()
|
|
37
38
|
tree.branch_length = data.get("branch_length", 1)
|
|
39
|
+
tree.name = data.get("name", "")
|
|
38
40
|
tree.children = [cls.from_dict(child) for child in data.get("children", [])]
|
|
39
41
|
return tree
|
|
40
42
|
|
iplotx/ingest/typing.py
CHANGED
|
@@ -254,12 +254,12 @@ class TreeDataProvider(Protocol):
|
|
|
254
254
|
self,
|
|
255
255
|
layout: str | LayoutType,
|
|
256
256
|
layout_style: Optional[dict[str, int | float | str]] = None,
|
|
257
|
-
directed: bool
|
|
257
|
+
directed: bool = False,
|
|
258
258
|
vertex_labels: Optional[Sequence[str] | dict[Hashable, str] | pd.Series | bool] = None,
|
|
259
259
|
edge_labels: Optional[Sequence[str] | dict] = None,
|
|
260
260
|
leaf_labels: Optional[Sequence[str] | dict[Hashable, str] | pd.Series | bool] = None,
|
|
261
261
|
) -> TreeData:
|
|
262
|
-
"""Create tree data object for iplotx from
|
|
262
|
+
"""Create tree data object for iplotx from any tree provider.
|
|
263
263
|
|
|
264
264
|
NOTE: This function needs NOT be implemented by individual providers.
|
|
265
265
|
"""
|
|
@@ -314,15 +314,17 @@ class TreeDataProvider(Protocol):
|
|
|
314
314
|
edge_data = {"_ipx_source": [], "_ipx_target": []}
|
|
315
315
|
for node in self.preorder():
|
|
316
316
|
for child in self.get_children(node):
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
edge_data["_ipx_target"].append(node)
|
|
320
|
-
else:
|
|
321
|
-
edge_data["_ipx_source"].append(node)
|
|
322
|
-
edge_data["_ipx_target"].append(child)
|
|
317
|
+
edge_data["_ipx_source"].append(node)
|
|
318
|
+
edge_data["_ipx_target"].append(child)
|
|
323
319
|
edge_df = pd.DataFrame(edge_data)
|
|
324
320
|
tree_data["edge_df"] = edge_df
|
|
325
321
|
|
|
322
|
+
# Add edge labels
|
|
323
|
+
# NOTE: Partial support only for now, only lists
|
|
324
|
+
if edge_labels is not None:
|
|
325
|
+
# Cycling sequence
|
|
326
|
+
edge_df["label"] = [edge_labels[i % len(edge_labels)] for i in range(len(edge_df))]
|
|
327
|
+
|
|
326
328
|
# Add branch support
|
|
327
329
|
if hasattr(self, "get_support"):
|
|
328
330
|
support = self.get_support()
|
iplotx/label.py
CHANGED
|
@@ -77,6 +77,12 @@ class LabelCollection(mpl.artist.Artist):
|
|
|
77
77
|
child.set_figure(fig)
|
|
78
78
|
self._update_offsets(dpi=fig.dpi)
|
|
79
79
|
|
|
80
|
+
def get_texts(self):
|
|
81
|
+
"""Get the texts of the labels."""
|
|
82
|
+
return [child.get_text() for child in self.get_children()]
|
|
83
|
+
|
|
84
|
+
get_text = get_texts
|
|
85
|
+
|
|
80
86
|
def _get_margins_with_dpi(self, dpi: float = 72.0) -> np.ndarray:
|
|
81
87
|
return self._margins * dpi / 72.0
|
|
82
88
|
|
iplotx/plotting.py
CHANGED
|
@@ -139,8 +139,7 @@ def tree(
|
|
|
139
139
|
Parameters:
|
|
140
140
|
tree: The tree to plot. Can be a BioPython.Phylo.Tree object.
|
|
141
141
|
layout: The layout to use for plotting.
|
|
142
|
-
directed: If False,
|
|
143
|
-
node. If "parent", draw arrows the other way around.
|
|
142
|
+
directed: If False, do not draw arrows.
|
|
144
143
|
show_support: If True, show the support values for the nodes (assumed to be from 0 to 100,
|
|
145
144
|
rounded to nearest integer). If both this parameter and vertex_labels are set,
|
|
146
145
|
show_support takes precedence and hides the vertex labels.
|
iplotx/style/__init__.py
CHANGED
|
@@ -60,7 +60,7 @@ def get_style(name: str = "", *args) -> dict[str, Any]:
|
|
|
60
60
|
style = style[namei]
|
|
61
61
|
# NOTE: if asking for a nonexistent, non-leaf style
|
|
62
62
|
# give the benefit of the doubt and set an empty dict
|
|
63
|
-
# which will not fail unless the
|
|
63
|
+
# which will not fail unless the user tries to enter it
|
|
64
64
|
elif namei not in style_leaves:
|
|
65
65
|
style = {}
|
|
66
66
|
elif len(args) > 0:
|
iplotx/style/leaf_info.py
CHANGED
|
@@ -22,6 +22,7 @@ rotating_leaves = (
|
|
|
22
22
|
"vpadding",
|
|
23
23
|
"hmargin",
|
|
24
24
|
"vmargin",
|
|
25
|
+
"padding",
|
|
25
26
|
"ports",
|
|
26
27
|
"width",
|
|
27
28
|
"height",
|
|
@@ -38,6 +39,7 @@ nonrotating_leaves = (
|
|
|
38
39
|
"deep",
|
|
39
40
|
"angular",
|
|
40
41
|
"curved",
|
|
42
|
+
"capstyle",
|
|
41
43
|
)
|
|
42
44
|
|
|
43
45
|
# Union of all style leaves (rotating and nonrotating)
|
iplotx/style/library.py
CHANGED
|
@@ -58,6 +58,28 @@ style_library = {
|
|
|
58
58
|
},
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
|
+
# Feedback, for regulatory networks
|
|
62
|
+
"feedback": {
|
|
63
|
+
"edge": {
|
|
64
|
+
"linewidth": 4,
|
|
65
|
+
"padding": 10,
|
|
66
|
+
"arrow": {
|
|
67
|
+
"marker": ")>",
|
|
68
|
+
"width": 20,
|
|
69
|
+
"height": 28,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
"vertex": {
|
|
73
|
+
"size": 35,
|
|
74
|
+
"color": None,
|
|
75
|
+
"facecolor": "white",
|
|
76
|
+
"edgecolor": "black",
|
|
77
|
+
"linewidth": 4,
|
|
78
|
+
"label": {
|
|
79
|
+
"color": "black",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
61
83
|
# Tree style, with zero-size vertices
|
|
62
84
|
"tree": {
|
|
63
85
|
"vertex": {
|
iplotx/tree.py
CHANGED
|
@@ -63,7 +63,7 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
63
63
|
self,
|
|
64
64
|
tree,
|
|
65
65
|
layout: Optional[str] = "horizontal",
|
|
66
|
-
directed: bool
|
|
66
|
+
directed: bool = False,
|
|
67
67
|
vertex_labels: Optional[bool | list[str] | dict[Hashable, str] | pd.Series] = None,
|
|
68
68
|
edge_labels: Optional[Sequence | dict[Hashable, str] | pd.Series] = None,
|
|
69
69
|
leaf_labels: Optional[Sequence | dict[Hashable, str]] | pd.Series = None,
|
|
@@ -76,8 +76,7 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
76
76
|
Parameters:
|
|
77
77
|
tree: The tree to plot.
|
|
78
78
|
layout: The layout to use for the tree. Can be "horizontal", "vertical", or "radial".
|
|
79
|
-
directed: Whether the tree is directed.
|
|
80
|
-
following choices: "parent" or "child".
|
|
79
|
+
directed: Whether the tree is directed. Must be a boolean.
|
|
81
80
|
vertex_labels: Labels for the vertices. Can be a list, dictionary, or pandas Series.
|
|
82
81
|
edge_labels: Labels for the edges. Can be a sequence of strings.
|
|
83
82
|
leaf_labels: Labels for the leaves. Can be a sequence of strings or a pandas Series.
|
|
@@ -294,7 +293,7 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
294
293
|
layout=self.get_layout(),
|
|
295
294
|
layout_coordinate_system=self._ipx_internal_data.get(
|
|
296
295
|
"layout_coordinate_system",
|
|
297
|
-
"
|
|
296
|
+
"cartesian",
|
|
298
297
|
),
|
|
299
298
|
style=get_style(".vertex"),
|
|
300
299
|
labels=self._get_label_series("vertex"),
|
|
@@ -586,10 +585,9 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
586
585
|
waypointsi = "y0x1"
|
|
587
586
|
elif layout_name == "radial":
|
|
588
587
|
waypointsi = "r0a1"
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
)
|
|
588
|
+
# NOTE: no need to catch the default case, it's caught
|
|
589
|
+
# when making the layout already. We should *never* be
|
|
590
|
+
# in an "else" case here.
|
|
593
591
|
waypoints.append(waypointsi)
|
|
594
592
|
|
|
595
593
|
# These are not the actual edges drawn, only stubs to establish
|
|
@@ -612,7 +610,6 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
612
610
|
else:
|
|
613
611
|
edge_style["waypoints"] = waypoints
|
|
614
612
|
|
|
615
|
-
# NOTE: Trees are directed is their "directed" property is True, "child", or "parent"
|
|
616
613
|
self._edges = EdgeCollection(
|
|
617
614
|
edgepatches,
|
|
618
615
|
vertex_ids=adjacent_vertex_ids,
|
iplotx/utils/matplotlib.py
CHANGED
|
@@ -90,6 +90,9 @@ def _get_label_width_height(text, hpadding=18, vpadding=12, dpi=72.0, **kwargs):
|
|
|
90
90
|
very accurate. Yet, it is often good enough and easier to implement than a careful
|
|
91
91
|
orchestration of Figure.draw_without_rendering.
|
|
92
92
|
"""
|
|
93
|
+
if len(text) == 0:
|
|
94
|
+
return (0, 0)
|
|
95
|
+
|
|
93
96
|
if "fontsize" in kwargs:
|
|
94
97
|
kwargs["size"] = kwargs.pop("fontsize")
|
|
95
98
|
forbidden_props = [
|
iplotx/version.py
CHANGED
|
@@ -2,37 +2,37 @@ iplotx/__init__.py,sha256=MKb9UCXKgDHHkeATuJWxYdM-AotfBo2fbWy-Rkbn9Is,509
|
|
|
2
2
|
iplotx/artists.py,sha256=Bpn6NS8S_B_E4OW88JYW6aEu2bIuIQJmbs2paTmBAoY,522
|
|
3
3
|
iplotx/cascades.py,sha256=OPqF7Huls-HFmDA5MCF6DEZlUeRVaXsbQcHBoKAgNJs,8182
|
|
4
4
|
iplotx/groups.py,sha256=_9KdIiTAi1kXtd2mDywgBJCbqoRq2z-5fzOPf76Wgb8,6287
|
|
5
|
-
iplotx/label.py,sha256=
|
|
5
|
+
iplotx/label.py,sha256=6am3a0ejcW_bWEXSOODE1Ke3AyCU1lJ45RfnXNbHAQw,8923
|
|
6
6
|
iplotx/layout.py,sha256=KxmRLqjo8AYCBAmXez8rIiLU2sM34qhb6ox9AHYwRyE,4839
|
|
7
7
|
iplotx/network.py,sha256=SlmDgc4tbCfvO08QWk-jUXrUfaz6S3xoXQVg6rP1910,11345
|
|
8
|
-
iplotx/plotting.py,sha256=
|
|
9
|
-
iplotx/tree.py,sha256=
|
|
8
|
+
iplotx/plotting.py,sha256=yACxkD6unKc5eDsAp7ZabRCAwLEXBowSMESX2oGNBDU,7291
|
|
9
|
+
iplotx/tree.py,sha256=S_9tf8Mixv9P5dq616tjxuxdDYRmUXLNAcSXTxEgm_I,27310
|
|
10
10
|
iplotx/typing.py,sha256=QLdzV358IiD1CFe88MVp0D77FSx5sSAVUmM_2WPPE8I,1463
|
|
11
|
-
iplotx/version.py,sha256=
|
|
11
|
+
iplotx/version.py,sha256=qs-N8bGi6RQubKakAGIJAWbOPniPVtj7B0gtM0s9tTc,70
|
|
12
12
|
iplotx/vertex.py,sha256=OjDIkJCNU-IhZUVeZTSzGwTlHLrxu27lUThiUuEb6Qs,14497
|
|
13
|
-
iplotx/edge/__init__.py,sha256=
|
|
14
|
-
iplotx/edge/arrow.py,sha256=
|
|
15
|
-
iplotx/edge/geometry.py,sha256=
|
|
13
|
+
iplotx/edge/__init__.py,sha256=VkAsuxphQa-co79MZWzWErkRAkp97CwB20ozPEnpvrM,26888
|
|
14
|
+
iplotx/edge/arrow.py,sha256=C4XoHGCYou1z2alz5Q2VhdaWYEzgebtEF70zVYY_frk,15533
|
|
15
|
+
iplotx/edge/geometry.py,sha256=tiaF4PzvsNBoROrEgcCsw0YdxxZr3oBxF4ord_k4ThA,15069
|
|
16
16
|
iplotx/edge/leaf.py,sha256=SyGMv2PIOoH0pey8-aMVaZheK3hNe1Qz_okcyWbc4E4,4268
|
|
17
17
|
iplotx/edge/ports.py,sha256=BpkbiEhX4mPBBAhOv4jcKFG4Y8hxXz5GRtVLCC0jbtI,1235
|
|
18
18
|
iplotx/ingest/__init__.py,sha256=tsXDoa7Rs6Y1ulWtjCcUsO4tQIigeQ6ZMiU2PQDyhwQ,4751
|
|
19
19
|
iplotx/ingest/heuristics.py,sha256=715VqgfKek5LOJnu1vTo7RqPgCl-Bb8Cf6o7_Tt57fA,5797
|
|
20
|
-
iplotx/ingest/typing.py,sha256=
|
|
20
|
+
iplotx/ingest/typing.py,sha256=hVEcAREjFFFbAWsxRkQuvpy1B4L7JEv_NRVVmrEbUVk,13984
|
|
21
21
|
iplotx/ingest/providers/network/igraph.py,sha256=8dWeaQ_ZNdltC098V2YeLXsGdJHQnBa6shF1GAfl0Zg,2973
|
|
22
22
|
iplotx/ingest/providers/network/networkx.py,sha256=FIXMI3hXU1WtAzPVlQZcz47b-4V2omeHttnNTgS2gQw,4328
|
|
23
|
-
iplotx/ingest/providers/network/simple.py,sha256=
|
|
23
|
+
iplotx/ingest/providers/network/simple.py,sha256=e_aHhiHhN9DrMoNrt7tEMPURXGhQ1TYRPzsxDEptUlc,3766
|
|
24
24
|
iplotx/ingest/providers/tree/biopython.py,sha256=4N_54cVyHHPcASJZGr6pHKE2p5R3i8Cm307SLlSLHLA,1480
|
|
25
25
|
iplotx/ingest/providers/tree/cogent3.py,sha256=JmELbDK7LyybiJzFNbmeqZ4ySJoDajvFfJebpNfFKWo,1073
|
|
26
26
|
iplotx/ingest/providers/tree/ete4.py,sha256=D7usSq0MOjzrk3EoLi834IlaDGwv7_qG6Qt0ptfKqfI,928
|
|
27
|
-
iplotx/ingest/providers/tree/simple.py,sha256=
|
|
27
|
+
iplotx/ingest/providers/tree/simple.py,sha256=aV9wGqBomJ5klM_aJQeuL_Q_J1pLCv6AFN98BPDiKUw,2593
|
|
28
28
|
iplotx/ingest/providers/tree/skbio.py,sha256=O1KUr8tYi28pZ3VVjapgO4Uj-YpMuix3GhOH5je8Lv4,822
|
|
29
|
-
iplotx/style/__init__.py,sha256=
|
|
30
|
-
iplotx/style/leaf_info.py,sha256=
|
|
31
|
-
iplotx/style/library.py,sha256=
|
|
29
|
+
iplotx/style/__init__.py,sha256=XMkQZ1U63wVNo98Zo5uJAn-uQgW2OTZABAizJqiuB3s,12253
|
|
30
|
+
iplotx/style/leaf_info.py,sha256=JoX1cPjRM_k3f93jzUPQ3gPlVP4wY_n032nOVhrgelU,969
|
|
31
|
+
iplotx/style/library.py,sha256=wO-eeY3EZfAl0v21aX9f5_MiZhHuL2kGsBYA3uJkIGs,8535
|
|
32
32
|
iplotx/utils/geometry.py,sha256=UH2gAcM5rYW7ADnJEm7HIJTpPF4UOm8P3vjSVCOGjqM,9192
|
|
33
33
|
iplotx/utils/internal.py,sha256=WWfcZDGK8Ut1y_tOHRGg9wSqY1bwSeLQO7dHM_8Tvwo,107
|
|
34
|
-
iplotx/utils/matplotlib.py,sha256=
|
|
34
|
+
iplotx/utils/matplotlib.py,sha256=TutVJ1dEWYgX_-CY6MvdhRvWYqxpByGb3TKrSByYPNM,5330
|
|
35
35
|
iplotx/utils/style.py,sha256=wMWxJykxBD-JmcN8-rSKlWcV6pMfwKgR4EzSpk_NX8k,547
|
|
36
|
-
iplotx-0.5.
|
|
37
|
-
iplotx-0.5.
|
|
38
|
-
iplotx-0.5.
|
|
36
|
+
iplotx-0.5.2.dev0.dist-info/METADATA,sha256=D_Wrepyok3RsInEki56AiTbAM63Zn4DyvfcFy8v4J0E,4894
|
|
37
|
+
iplotx-0.5.2.dev0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
38
|
+
iplotx-0.5.2.dev0.dist-info/RECORD,,
|
|
File without changes
|