iplotx 0.5.0__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 -44
- 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.0.dist-info → iplotx-0.5.2.dev0.dist-info}/METADATA +32 -15
- {iplotx-0.5.0.dist-info → iplotx-0.5.2.dev0.dist-info}/RECORD +17 -17
- {iplotx-0.5.0.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,46 +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
|
-
|
|
103
|
-
|
|
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
|
|
104
112
|
|
|
105
113
|
|
|
106
114
|
def _fix_parallel_edges_straight(
|
|
@@ -137,11 +145,12 @@ def _compute_loop_path(
|
|
|
137
145
|
angle2,
|
|
138
146
|
trans_inv,
|
|
139
147
|
looptension,
|
|
148
|
+
padding=0,
|
|
140
149
|
):
|
|
141
150
|
# Shorten at starting angle
|
|
142
|
-
start = _get_shorter_edge_coords(vpath, vsize, angle1) + vcoord_fig
|
|
151
|
+
start = _get_shorter_edge_coords(vpath, vsize, angle1, padding) + vcoord_fig
|
|
143
152
|
# Shorten at end angle
|
|
144
|
-
end = _get_shorter_edge_coords(vpath, vsize, angle2) + vcoord_fig
|
|
153
|
+
end = _get_shorter_edge_coords(vpath, vsize, angle2, padding) + vcoord_fig
|
|
145
154
|
|
|
146
155
|
aux1 = (start - vcoord_fig) * looptension + vcoord_fig
|
|
147
156
|
aux2 = (end - vcoord_fig) * looptension + vcoord_fig
|
|
@@ -173,6 +182,7 @@ def _compute_edge_path_straight(
|
|
|
173
182
|
trans,
|
|
174
183
|
trans_inv,
|
|
175
184
|
layout_coordinate_system: str = "cartesian",
|
|
185
|
+
padding: float = 0,
|
|
176
186
|
**kwargs,
|
|
177
187
|
):
|
|
178
188
|
if layout_coordinate_system not in ("cartesian", "polar"):
|
|
@@ -199,14 +209,13 @@ def _compute_edge_path_straight(
|
|
|
199
209
|
|
|
200
210
|
# Angle of the straight line
|
|
201
211
|
theta = atan2(*((vcoord_fig[1] - vcoord_fig[0])[::-1]))
|
|
202
|
-
print(vcoord_data_cart, vcoord_fig, theta)
|
|
203
212
|
|
|
204
213
|
# Shorten at starting vertex
|
|
205
|
-
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]
|
|
206
215
|
points.append(vs)
|
|
207
216
|
|
|
208
217
|
# Shorten at end vertex
|
|
209
|
-
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]
|
|
210
219
|
points.append(ve)
|
|
211
220
|
|
|
212
221
|
codes = ["MOVETO", "LINETO"]
|
|
@@ -228,9 +237,41 @@ def _compute_edge_path_waypoints(
|
|
|
228
237
|
layout_coordinate_system: str = "cartesian",
|
|
229
238
|
points_per_curve: int = 30,
|
|
230
239
|
ports: Pair[Optional[str]] = (None, None),
|
|
240
|
+
padding: float = 0,
|
|
231
241
|
**kwargs,
|
|
232
242
|
):
|
|
233
|
-
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"):
|
|
234
275
|
assert layout_coordinate_system == "cartesian"
|
|
235
276
|
|
|
236
277
|
# Coordinates in figure (default) coords
|
|
@@ -252,7 +293,8 @@ def _compute_edge_path_waypoints(
|
|
|
252
293
|
|
|
253
294
|
# Shorten at vertex border
|
|
254
295
|
vshorts[i] = (
|
|
255
|
-
_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]
|
|
256
298
|
)
|
|
257
299
|
|
|
258
300
|
# Shorten waypoints to keep the angles right
|
|
@@ -300,7 +342,9 @@ def _compute_edge_path_waypoints(
|
|
|
300
342
|
theta = atan2(*(_get_port_unit_vector(ports[i], trans_inv)[::-1]))
|
|
301
343
|
|
|
302
344
|
# Shorten at vertex border
|
|
303
|
-
vshort =
|
|
345
|
+
vshort = (
|
|
346
|
+
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], theta, padding) + vcoord_fig[i]
|
|
347
|
+
)
|
|
304
348
|
thetas.append(theta)
|
|
305
349
|
vshorts.append(vshort)
|
|
306
350
|
|
|
@@ -347,6 +391,7 @@ def _compute_edge_path_curved(
|
|
|
347
391
|
trans,
|
|
348
392
|
trans_inv,
|
|
349
393
|
ports: Pair[Optional[str]] = (None, None),
|
|
394
|
+
padding: float = 0,
|
|
350
395
|
):
|
|
351
396
|
"""Shorten the edge path along a cubic Bezier between the vertex centres.
|
|
352
397
|
|
|
@@ -399,7 +444,9 @@ def _compute_edge_path_curved(
|
|
|
399
444
|
thetas = [None, None]
|
|
400
445
|
for i in range(2):
|
|
401
446
|
thetas[i] = atan2(*((auxs[i] - vcoord_fig[i])[::-1]))
|
|
402
|
-
vs[i] =
|
|
447
|
+
vs[i] = (
|
|
448
|
+
_get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i], padding) + vcoord_fig[i]
|
|
449
|
+
)
|
|
403
450
|
|
|
404
451
|
path = {
|
|
405
452
|
"vertices": [
|
|
@@ -424,7 +471,7 @@ def _compute_edge_path_curved(
|
|
|
424
471
|
def _compute_edge_path(
|
|
425
472
|
*args,
|
|
426
473
|
tension: float = 0,
|
|
427
|
-
waypoints: str = "none",
|
|
474
|
+
waypoints: str | tuple[float, float] | Sequence[tuple[float, float]] | np.ndarray = "none",
|
|
428
475
|
ports: Pair[Optional[str]] = (None, None),
|
|
429
476
|
layout_coordinate_system: str = "cartesian",
|
|
430
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iplotx
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2.dev0
|
|
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
|
|
@@ -43,9 +43,25 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|

|
|
44
44
|
|
|
45
45
|
# iplotx
|
|
46
|
-
|
|
46
|
+
[](https://iplotx.readthedocs.io/en/latest/gallery/index.html).
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
Visualise networks and trees in Python, with style.
|
|
49
|
+
|
|
50
|
+
Supports:
|
|
51
|
+
- **networks**:
|
|
52
|
+
- [networkx](https://networkx.org/)
|
|
53
|
+
- [igraph](igraph.readthedocs.io/)
|
|
54
|
+
- [minimal network data structure](https://iplotx.readthedocs.io/en/latest/gallery/plot_simplenetworkdataprovider.html#sphx-glr-gallery-plot-simplenetworkdataprovider-py) (for educational purposes)
|
|
55
|
+
- **trees**:
|
|
56
|
+
- [ETE4](https://etetoolkit.github.io/ete/)
|
|
57
|
+
- [cogent3](https://cogent3.org/)
|
|
58
|
+
- [Biopython](https://biopython.org/)
|
|
59
|
+
- [scikit-bio](https://scikit.bio)
|
|
60
|
+
- [minimal tree data structure](https://iplotx.readthedocs.io/en/latest/gallery/tree/plot_simpletreedataprovider.html#sphx-glr-gallery-tree-plot-simpletreedataprovider-py) (for educational purposes)
|
|
61
|
+
|
|
62
|
+
In addition to the above, *any* network or tree analysis library can register an [entry point](https://iplotx.readthedocs.io/en/latest/providers.html#creating-a-custom-data-provider) to gain compatibility with `iplotx` with no intervention from our side.
|
|
63
|
+
|
|
64
|
+
**NOTE**: This is currently late beta quality software. The API and functionality might break rarely.
|
|
49
65
|
|
|
50
66
|
## Installation
|
|
51
67
|
```bash
|
|
@@ -72,19 +88,20 @@ See [readthedocs](https://iplotx.readthedocs.io/en/latest/) for the full documen
|
|
|
72
88
|
## Gallery
|
|
73
89
|
See [gallery](https://iplotx.readthedocs.io/en/latest/gallery/index.html).
|
|
74
90
|
|
|
75
|
-
##
|
|
76
|
-
- Plot networks from
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
91
|
+
## Features
|
|
92
|
+
- Plot networks from multiple libraries including networkx and igraph, using matplotlib as a backend. ✅
|
|
93
|
+
- Plot trees from multiple libraries such as cogent3, ETE4, skbio, and biopython. ✅
|
|
94
|
+
- Flexible yet easy styling, including an internal library of styles ✅
|
|
95
|
+
- Interactive plotting, e.g. zooming and panning after the plot is created. ✅
|
|
96
|
+
- Store the plot to disk thanks to the many matplotlib backends (SVG, PNG, PDF, etc.). ✅
|
|
80
97
|
- Efficient plotting of large graphs using matplotlib's collection functionality. ✅
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
- Edit plotting elements after the plot is created, e.g. changing node colors, labels, etc. ✅
|
|
99
|
+
- Animations, e.g. showing the evolution of a network over time. ✅
|
|
100
|
+
- Mouse and keyboard interaction, e.g. hovering over nodes/edges to get information about them. ✅
|
|
101
|
+
- Node clustering and covers, e.g. showing communities in a network. ✅
|
|
102
|
+
- Choice of tree layouts and orientations. ✅
|
|
103
|
+
- Tree-specific options: cascades, subtree styling, split edges, etc. ✅
|
|
104
|
+
- (WIP) Support uni- and bi-directional communication between graph object and plot object.🏗️
|
|
88
105
|
|
|
89
106
|
## Authors
|
|
90
107
|
Fabio Zanini (https://fabilab.org)
|
|
@@ -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
|