iplotx 0.7.0__py3-none-any.whl → 0.8.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/network.py +8 -0
- iplotx/style/__init__.py +13 -83
- iplotx/tree.py +14 -0
- iplotx/utils/style.py +111 -0
- iplotx/version.py +1 -1
- {iplotx-0.7.0.dist-info → iplotx-0.8.0.dist-info}/METADATA +1 -1
- {iplotx-0.7.0.dist-info → iplotx-0.8.0.dist-info}/RECORD +8 -8
- {iplotx-0.7.0.dist-info → iplotx-0.8.0.dist-info}/WHEEL +0 -0
iplotx/network.py
CHANGED
|
@@ -162,6 +162,8 @@ class NetworkArtist(mpl.artist.Artist):
|
|
|
162
162
|
"""Get VertexCollection artist."""
|
|
163
163
|
return self._vertices
|
|
164
164
|
|
|
165
|
+
get_nodes = get_vertices
|
|
166
|
+
|
|
165
167
|
def get_edges(self):
|
|
166
168
|
"""Get EdgeCollection artist."""
|
|
167
169
|
return self._edges
|
|
@@ -170,6 +172,8 @@ class NetworkArtist(mpl.artist.Artist):
|
|
|
170
172
|
"""Get list of vertex label artists."""
|
|
171
173
|
return self._vertices.get_labels()
|
|
172
174
|
|
|
175
|
+
get_node_labels = get_vertex_labels
|
|
176
|
+
|
|
173
177
|
def get_edge_labels(self):
|
|
174
178
|
"""Get list of edge label artists."""
|
|
175
179
|
return self._edges.get_labels()
|
|
@@ -211,6 +215,10 @@ class NetworkArtist(mpl.artist.Artist):
|
|
|
211
215
|
return vertex_layout_df
|
|
212
216
|
|
|
213
217
|
def _get_label_series(self, kind):
|
|
218
|
+
# Equivalence vertex/node
|
|
219
|
+
if kind == "node":
|
|
220
|
+
kind = "vertex"
|
|
221
|
+
|
|
214
222
|
if "label" in self._ipx_internal_data[f"{kind}_df"].columns:
|
|
215
223
|
return self._ipx_internal_data[f"{kind}_df"]["label"]
|
|
216
224
|
else:
|
iplotx/style/__init__.py
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main style module for iplotx.
|
|
3
|
+
"""
|
|
4
|
+
|
|
1
5
|
from typing import (
|
|
2
6
|
Any,
|
|
3
7
|
Iterable,
|
|
4
8
|
Optional,
|
|
5
9
|
Sequence,
|
|
6
10
|
)
|
|
7
|
-
from collections import defaultdict
|
|
8
11
|
from collections.abc import Hashable
|
|
9
12
|
from contextlib import contextmanager
|
|
10
13
|
import numpy as np
|
|
11
14
|
import pandas as pd
|
|
12
15
|
|
|
13
|
-
from ..utils.style import
|
|
16
|
+
from ..utils.style import (
|
|
17
|
+
copy_with_deep_values,
|
|
18
|
+
sanitize_leaves,
|
|
19
|
+
update_style,
|
|
20
|
+
sanitize_ambiguous,
|
|
21
|
+
)
|
|
14
22
|
from .library import style_library
|
|
15
23
|
from .leaf_info import (
|
|
16
24
|
style_leaves,
|
|
@@ -83,94 +91,16 @@ def merge_styles(
|
|
|
83
91
|
Returns:
|
|
84
92
|
The composite style as a dict.
|
|
85
93
|
"""
|
|
86
|
-
try:
|
|
87
|
-
import networkx as nx
|
|
88
|
-
except ImportError:
|
|
89
|
-
nx = None
|
|
90
|
-
|
|
91
|
-
def _sanitize_leaves(style: dict):
|
|
92
|
-
for key, value in style.items():
|
|
93
|
-
if key in style_leaves:
|
|
94
|
-
# Networkx has a few lazy data structures
|
|
95
|
-
# TODO: move this code to provider
|
|
96
|
-
if nx is not None:
|
|
97
|
-
if isinstance(value, nx.classes.reportviews.NodeView):
|
|
98
|
-
style[key] = dict(value)
|
|
99
|
-
elif isinstance(value, nx.classes.reportviews.EdgeViewABC):
|
|
100
|
-
style[key] = [v for *e, v in value]
|
|
101
|
-
|
|
102
|
-
elif isinstance(value, dict):
|
|
103
|
-
_sanitize_leaves(value)
|
|
104
|
-
|
|
105
|
-
def _update(style: dict, current: dict):
|
|
106
|
-
for key, value in style.items():
|
|
107
|
-
if key not in current:
|
|
108
|
-
current[key] = value
|
|
109
|
-
continue
|
|
110
|
-
|
|
111
|
-
# Style non-leaves are either recurred into or deleted
|
|
112
|
-
if key not in style_leaves:
|
|
113
|
-
if isinstance(value, dict):
|
|
114
|
-
_update(value, current[key])
|
|
115
|
-
elif value is None:
|
|
116
|
-
del current[key]
|
|
117
|
-
else:
|
|
118
|
-
raise ValueError(
|
|
119
|
-
f"Setting non-leaf style value to a non-dict: {key}, {value}",
|
|
120
|
-
)
|
|
121
|
-
else:
|
|
122
|
-
# Style leaves could be incomplete, ensure a sensible default
|
|
123
|
-
if value is None:
|
|
124
|
-
del current[key]
|
|
125
|
-
continue
|
|
126
|
-
|
|
127
|
-
if not isinstance(value, dict):
|
|
128
|
-
current[key] = value
|
|
129
|
-
continue
|
|
130
|
-
|
|
131
|
-
if hasattr(value, "default_factory"):
|
|
132
|
-
current[key] = value
|
|
133
|
-
continue
|
|
134
|
-
|
|
135
|
-
if hasattr(current[key], "default_factory"):
|
|
136
|
-
default_value = current[key].default_factory()
|
|
137
|
-
else:
|
|
138
|
-
default_value = current[key]
|
|
139
|
-
current[key] = defaultdict(
|
|
140
|
-
lambda: default_value,
|
|
141
|
-
value,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
def _sanitize_ambiguous(style: dict):
|
|
145
|
-
"""Fix a few ambiguous cases in the style dict."""
|
|
146
|
-
|
|
147
|
-
# Accept "node" style as "vertex" style for user flexibility
|
|
148
|
-
if "node" in style:
|
|
149
|
-
style_node = style.pop("node")
|
|
150
|
-
if "vertex" not in style:
|
|
151
|
-
style["vertex"] = style_node
|
|
152
|
-
else:
|
|
153
|
-
# "node" style applies on TOP of "vertex" style
|
|
154
|
-
_update(style_node, style["vertex"])
|
|
155
|
-
|
|
156
|
-
# NOTE: Deprecate edge_padding for edge_shrink
|
|
157
|
-
for edgekey in ["edge", "leafedge"]:
|
|
158
|
-
if "padding" in style.get(edgekey, {}):
|
|
159
|
-
# shrink takes over
|
|
160
|
-
if "shrink" in style[edgekey]:
|
|
161
|
-
del style[edgekey]["padding"]
|
|
162
|
-
else:
|
|
163
|
-
style[edgekey]["shrink"] = style[edgekey].pop("padding")
|
|
164
94
|
|
|
165
95
|
merged = {}
|
|
166
96
|
for style in styles:
|
|
167
97
|
if isinstance(style, str):
|
|
168
98
|
style = get_style(style)
|
|
169
99
|
else:
|
|
170
|
-
|
|
100
|
+
sanitize_leaves(style)
|
|
171
101
|
unflatten_style(style)
|
|
172
|
-
|
|
173
|
-
|
|
102
|
+
sanitize_ambiguous(style)
|
|
103
|
+
update_style(style, merged)
|
|
174
104
|
|
|
175
105
|
return merged
|
|
176
106
|
|
iplotx/tree.py
CHANGED
|
@@ -193,6 +193,10 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
193
193
|
"""Get vertex or edge layout."""
|
|
194
194
|
layout_columns = [f"_ipx_layout_{i}" for i in range(self._ipx_internal_data["ndim"])]
|
|
195
195
|
|
|
196
|
+
# Equivalence vertex <-> node
|
|
197
|
+
if kind == "node":
|
|
198
|
+
kind = "vertex"
|
|
199
|
+
|
|
196
200
|
if kind == "vertex":
|
|
197
201
|
layout = self._ipx_internal_data["vertex_df"][layout_columns]
|
|
198
202
|
return layout
|
|
@@ -245,6 +249,10 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
245
249
|
return bbox
|
|
246
250
|
|
|
247
251
|
def _get_label_series(self, kind: str) -> Optional[pd.Series]:
|
|
252
|
+
# Equivalence vertex <-> node
|
|
253
|
+
if kind == "node":
|
|
254
|
+
kind = "vertex"
|
|
255
|
+
|
|
248
256
|
if "label" in self._ipx_internal_data[f"{kind}_df"].columns:
|
|
249
257
|
return self._ipx_internal_data[f"{kind}_df"]["label"]
|
|
250
258
|
else:
|
|
@@ -254,6 +262,8 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
254
262
|
"""Get VertexCollection artist."""
|
|
255
263
|
return self._vertices
|
|
256
264
|
|
|
265
|
+
get_nodes = get_vertices
|
|
266
|
+
|
|
257
267
|
def get_edges(self) -> EdgeCollection:
|
|
258
268
|
"""Get EdgeCollection artist."""
|
|
259
269
|
return self._edges
|
|
@@ -262,6 +272,8 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
262
272
|
"""Get leaf VertexCollection artist."""
|
|
263
273
|
return self._leaf_vertices
|
|
264
274
|
|
|
275
|
+
get_leaf_nodes = get_leaf_vertices
|
|
276
|
+
|
|
265
277
|
def get_leaf_edges(self) -> Optional[LeafEdgeCollection]:
|
|
266
278
|
"""Get LeafEdgeCollection artist if present."""
|
|
267
279
|
if hasattr(self, "_leaf_edges"):
|
|
@@ -272,6 +284,8 @@ class TreeArtist(mpl.artist.Artist):
|
|
|
272
284
|
"""Get list of vertex label artists."""
|
|
273
285
|
return self._vertices.get_labels()
|
|
274
286
|
|
|
287
|
+
get_node_labels = get_vertex_labels
|
|
288
|
+
|
|
275
289
|
def get_edge_labels(self) -> LabelCollection:
|
|
276
290
|
"""Get list of edge label artists."""
|
|
277
291
|
return self._edges.get_labels()
|
iplotx/utils/style.py
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
from collections import defaultdict
|
|
3
3
|
|
|
4
|
+
from ..style.leaf_info import (
|
|
5
|
+
style_leaves,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import networkx as nx
|
|
10
|
+
except ImportError:
|
|
11
|
+
nx = None
|
|
12
|
+
|
|
4
13
|
|
|
5
14
|
def copy_with_deep_values(style):
|
|
6
15
|
"""Make a deep copy of the style dict but do not create copies of the keys."""
|
|
@@ -15,3 +24,105 @@ def copy_with_deep_values(style):
|
|
|
15
24
|
else:
|
|
16
25
|
newdict[key] = copy.copy(value)
|
|
17
26
|
return newdict
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def sanitize_leaves(style: dict):
|
|
30
|
+
"""Sanitize the leaves of a style dictionary.
|
|
31
|
+
|
|
32
|
+
Parameters:
|
|
33
|
+
style (dict): A style dictionary.
|
|
34
|
+
Returns:
|
|
35
|
+
None: The style dictionary is modified in place.
|
|
36
|
+
"""
|
|
37
|
+
for key, value in style.items():
|
|
38
|
+
if key in style_leaves:
|
|
39
|
+
# Networkx has a few lazy data structures
|
|
40
|
+
# TODO: move this code to provider
|
|
41
|
+
if nx is not None:
|
|
42
|
+
if isinstance(value, nx.classes.reportviews.NodeView):
|
|
43
|
+
style[key] = dict(value)
|
|
44
|
+
elif isinstance(value, nx.classes.reportviews.EdgeViewABC):
|
|
45
|
+
style[key] = [v for *e, v in value]
|
|
46
|
+
|
|
47
|
+
elif isinstance(value, dict):
|
|
48
|
+
sanitize_leaves(value)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def update_style(new_style: dict, current_style: dict):
|
|
52
|
+
"""Update the current style with a new style.
|
|
53
|
+
|
|
54
|
+
Parameters:
|
|
55
|
+
new_style (dict): A new style dictionary.
|
|
56
|
+
current_style (dict): The current style dictionary.
|
|
57
|
+
Returns:
|
|
58
|
+
None: The current style dictionary is modified in place.
|
|
59
|
+
"""
|
|
60
|
+
for key, value in new_style.items():
|
|
61
|
+
if key not in current_style:
|
|
62
|
+
current_style[key] = value
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
# Style non-leaves are either recurred into or deleted
|
|
66
|
+
if key not in style_leaves:
|
|
67
|
+
if isinstance(value, dict):
|
|
68
|
+
update_style(value, current_style[key])
|
|
69
|
+
elif value is None:
|
|
70
|
+
del current_style[key]
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"Setting non-leaf style value to a non-dict: {key}, {value}",
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
# Style leaves could be incomplete, ensure a sensible default
|
|
77
|
+
if value is None:
|
|
78
|
+
del current_style[key]
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
if not isinstance(value, dict):
|
|
82
|
+
current_style[key] = value
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
if hasattr(value, "default_factory"):
|
|
86
|
+
current_style[key] = value
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
if hasattr(current_style[key], "default_factory"):
|
|
90
|
+
default_value = current_style[key].default_factory()
|
|
91
|
+
else:
|
|
92
|
+
default_value = current_style[key]
|
|
93
|
+
current_style[key] = defaultdict(
|
|
94
|
+
lambda: default_value,
|
|
95
|
+
value,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def sanitize_ambiguous(style: dict):
|
|
100
|
+
"""Fix a few ambiguous cases in the style dict.
|
|
101
|
+
|
|
102
|
+
Parameters:
|
|
103
|
+
style (dict): A style dictionary. This must be unflattened beforehand.
|
|
104
|
+
Returns:
|
|
105
|
+
None: The style dictionary is modified in place.
|
|
106
|
+
|
|
107
|
+
NOTE: This function exists by design, not accident. It is useful for purposeful
|
|
108
|
+
(e.g. node vs vertex) or historical (e.g. edge_padding vs edge_shrink) reasons.
|
|
109
|
+
Either way, it softens the user experience without complicating the API.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
# Accept "node" style as "vertex" style for user flexibility
|
|
113
|
+
if "node" in style:
|
|
114
|
+
style_node = style.pop("node")
|
|
115
|
+
if "vertex" not in style:
|
|
116
|
+
style["vertex"] = style_node
|
|
117
|
+
else:
|
|
118
|
+
# "node" style applies on TOP of "vertex" style
|
|
119
|
+
update_style(style_node, style["vertex"])
|
|
120
|
+
|
|
121
|
+
# NOTE: Deprecate edge_padding for edge_shrink
|
|
122
|
+
for edgekey in ["edge", "leafedge"]:
|
|
123
|
+
if "padding" in style.get(edgekey, {}):
|
|
124
|
+
# shrink takes over
|
|
125
|
+
if "shrink" in style[edgekey]:
|
|
126
|
+
del style[edgekey]["padding"]
|
|
127
|
+
else:
|
|
128
|
+
style[edgekey]["shrink"] = style[edgekey].pop("padding")
|
iplotx/version.py
CHANGED
|
@@ -4,11 +4,11 @@ iplotx/cascades.py,sha256=OPqF7Huls-HFmDA5MCF6DEZlUeRVaXsbQcHBoKAgNJs,8182
|
|
|
4
4
|
iplotx/groups.py,sha256=_9KdIiTAi1kXtd2mDywgBJCbqoRq2z-5fzOPf76Wgb8,6287
|
|
5
5
|
iplotx/label.py,sha256=6am3a0ejcW_bWEXSOODE1Ke3AyCU1lJ45RfnXNbHAQw,8923
|
|
6
6
|
iplotx/layout.py,sha256=KxmRLqjo8AYCBAmXez8rIiLU2sM34qhb6ox9AHYwRyE,4839
|
|
7
|
-
iplotx/network.py,sha256=
|
|
7
|
+
iplotx/network.py,sha256=SGmXXrFxqgOoQcEJSZdL3WiI6rHDlPOGg5loGPrYpDk,11688
|
|
8
8
|
iplotx/plotting.py,sha256=IEUxW1xzTljLjBfsVP2BNsOPCDpj5bEPZ99bzvD5-mo,10066
|
|
9
|
-
iplotx/tree.py,sha256
|
|
9
|
+
iplotx/tree.py,sha256=TxbNoBHS0CfswrcMIWCNtnOl_3e4-PwCrVo0goywC0U,28807
|
|
10
10
|
iplotx/typing.py,sha256=QLdzV358IiD1CFe88MVp0D77FSx5sSAVUmM_2WPPE8I,1463
|
|
11
|
-
iplotx/version.py,sha256=
|
|
11
|
+
iplotx/version.py,sha256=tH4d2ah5uIPOD3rmxSuQMJ0PfIEEE0SOFEJKo0zwzXU,66
|
|
12
12
|
iplotx/vertex.py,sha256=hqdlD9fRBSwH5bRvlpaaPu7jgUR4z9nob1SYfPWDxtI,14966
|
|
13
13
|
iplotx/edge/__init__.py,sha256=AVnLsrDWWCkix1LVhrjpWKEKDxOp8joM4tF6RqEHC8I,27115
|
|
14
14
|
iplotx/edge/arrow.py,sha256=ZKt3UNZ7XRa2S3KxpoQfd4q_6eSUHOS476BZNqlf2pw,16462
|
|
@@ -26,13 +26,13 @@ iplotx/ingest/providers/tree/cogent3.py,sha256=JmELbDK7LyybiJzFNbmeqZ4ySJoDajvFf
|
|
|
26
26
|
iplotx/ingest/providers/tree/ete4.py,sha256=D7usSq0MOjzrk3EoLi834IlaDGwv7_qG6Qt0ptfKqfI,928
|
|
27
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=
|
|
29
|
+
iplotx/style/__init__.py,sha256=rf1GutrE8hHUhCoe4FGKYX-aNtHuu_U-kYQnqUxZNrY,10282
|
|
30
30
|
iplotx/style/leaf_info.py,sha256=mcd6ewZl3jC0CPshmbeUkNp2geoihJW9515roGy2T8o,1000
|
|
31
31
|
iplotx/style/library.py,sha256=58Y8BlllGLsR4pQM7_PVCP5tH6_4GkchXZvJpqGHlcg,8534
|
|
32
32
|
iplotx/utils/geometry.py,sha256=6RrC6qaB0-1vIk1LhGA4CfsiMd-9JNniSPyL_l9mshE,9245
|
|
33
33
|
iplotx/utils/internal.py,sha256=WWfcZDGK8Ut1y_tOHRGg9wSqY1bwSeLQO7dHM_8Tvwo,107
|
|
34
34
|
iplotx/utils/matplotlib.py,sha256=wELE73quQv10-1w9uA5eDTgkZkylJvjg7pd3K5tZPOo,6294
|
|
35
|
-
iplotx/utils/style.py,sha256=
|
|
36
|
-
iplotx-0.
|
|
37
|
-
iplotx-0.
|
|
38
|
-
iplotx-0.
|
|
35
|
+
iplotx/utils/style.py,sha256=vyNP80nDYVinqm6_9ltCJCtjK35ZcGlHvOskNv3eQBc,4225
|
|
36
|
+
iplotx-0.8.0.dist-info/METADATA,sha256=L756OyuMisG3j2Y_OsgvcqUcCavwDbVAPvfwBg7S9dQ,4908
|
|
37
|
+
iplotx-0.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
38
|
+
iplotx-0.8.0.dist-info/RECORD,,
|
|
File without changes
|