iplotx 0.2.0__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/vertex.py CHANGED
@@ -6,7 +6,6 @@ from typing import (
6
6
  Optional,
7
7
  Sequence,
8
8
  Any,
9
- Never,
10
9
  )
11
10
  import warnings
12
11
  import numpy as np
@@ -70,20 +69,21 @@ class VertexCollection(PatchCollection):
70
69
  self._index = layout.index
71
70
  self._style = style
72
71
  self._labels = labels
72
+ self._layout = layout
73
+ self._layout_coordinate_system = layout_coordinate_system
73
74
 
74
75
  # Create patches from structured data
75
- patches, offsets, sizes, kwargs2 = self._init_vertex_patches(
76
- layout,
77
- layout_coordinate_system=layout_coordinate_system,
78
- )
76
+ patches, sizes, kwargs2 = self._init_vertex_patches()
79
77
 
80
78
  kwargs.update(kwargs2)
81
- kwargs["offsets"] = offsets
82
79
  kwargs["match_original"] = True
83
80
 
84
81
  # Pass to PatchCollection constructor
85
82
  super().__init__(patches, *args, **kwargs)
86
83
 
84
+ # Set offsets in coordinate system
85
+ self._update_offsets_from_layout()
86
+
87
87
  # Compute _transforms like in _CollectionWithScales for dpi issues
88
88
  self.set_sizes(sizes)
89
89
 
@@ -100,10 +100,11 @@ class VertexCollection(PatchCollection):
100
100
  children.append(self._label_collection)
101
101
  return tuple(children)
102
102
 
103
- def set_figure(self, fig) -> Never:
103
+ def set_figure(self, fig) -> None:
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
 
@@ -123,7 +124,7 @@ class VertexCollection(PatchCollection):
123
124
  """Get vertex sizes (max of width and height), scaled by dpi."""
124
125
  return self._transforms[:, 0, 0]
125
126
 
126
- def set_sizes(self, sizes, dpi=72.0):
127
+ def set_sizes(self, sizes, dpi: float = 72.0) -> None:
127
128
  """Set vertex sizes.
128
129
 
129
130
  This rescales the current vertex symbol/path linearly, using this
@@ -146,9 +147,74 @@ class VertexCollection(PatchCollection):
146
147
  get_size = get_sizes
147
148
  set_size = set_sizes
148
149
 
149
- def _init_vertex_patches(
150
- self, vertex_layout_df, layout_coordinate_system="cartesian"
151
- ):
150
+ def get_layout(self) -> pd.DataFrame:
151
+ """Get the vertex layout.
152
+
153
+ Returns:
154
+ The vertex layout as a DataFrame.
155
+ """
156
+ return self._layout
157
+
158
+ def get_layout_coordinate_system(self) -> str:
159
+ """Get the layout coordinate system.
160
+
161
+ Returns:
162
+ Name of the layout coordinate system, e.g. "cartesian" or "polar".
163
+ """
164
+ return self._layout_coordinate_system
165
+
166
+ def get_offsets(self, ignore_layout: bool = True) -> np.ndarray:
167
+ """Get the vertex offsets.
168
+
169
+ Parameters:
170
+ ignore_layout: If True, return the matplotlib Artist._offsets directly, ignoring the
171
+ layout coordinate system. If False, it's equivalent to get_layout().values.
172
+
173
+ Returns:
174
+ The vertex offsets as a 2D numpy array.
175
+
176
+ Note: It is best for users to *not* ignore the layout coordinate system, as it may lead
177
+ to inconsistencies. However, some internal matplotlib functions require the default
178
+ signature of this function to look at the vanilla offsets, hence the default parameters.
179
+ """
180
+ if not ignore_layout:
181
+ return self.get_layout().values
182
+ else:
183
+ return self._offsets
184
+
185
+ def _update_offsets_from_layout(self) -> None:
186
+ """Update offsets in matplotlib coordinates from the layout DataFrame."""
187
+ if self._layout_coordinate_system == "cartesian":
188
+ self._offsets = self._layout.values
189
+ elif self._layout_coordinate_system == "polar":
190
+ # Convert polar coordinates (r, theta) to cartesian (x, y)
191
+ r = self._layout.iloc[:, 0].values
192
+ theta = self._layout.iloc[:, 1].values
193
+ if self._offsets is None:
194
+ self._offsets = np.zeros((len(r), 2))
195
+ self._offsets[:, 0] = r * np.cos(theta)
196
+ self._offsets[:, 1] = r * np.sin(theta)
197
+ else:
198
+ raise ValueError(
199
+ f"Layout coordinate system not supported: {self._layout_coordinate_system}."
200
+ )
201
+
202
+ def set_offsets(self, offsets: np.ndarray) -> None:
203
+ """Set the vertex positions/offsets in layout coordinates.
204
+
205
+ Parameters:
206
+ offsets: Array of coordinates in the layout coordinate system. For polar layouts,
207
+ these should be in the form of (r, theta) pairs.
208
+ """
209
+ self._layout.values[:] = offsets
210
+ self._update_offsets_from_layout()
211
+ self.stale = True
212
+
213
+ def get_style(self) -> Optional[dict[str, Any]]:
214
+ """Get the style dictionary for the vertices."""
215
+ return self._style
216
+
217
+ def _init_vertex_patches(self):
152
218
  style = self._style or {}
153
219
  if "cmap" in style:
154
220
  cmap_fun = _build_cmap_fun(
@@ -164,32 +230,17 @@ class VertexCollection(PatchCollection):
164
230
  "No labels found, cannot resize vertices based on labels."
165
231
  )
166
232
  style["size"] = get_style("default.vertex")["size"]
167
- else:
168
- vertex_labels = self._labels
169
233
 
170
234
  if "cmap" in style:
171
235
  colorarray = []
172
236
  patches = []
173
- offsets = []
174
237
  sizes = []
175
- for i, (vid, row) in enumerate(vertex_layout_df.iterrows()):
176
- # Centre of the vertex
177
- offset = list(row.values)
178
-
179
- # Transform to cartesian coordinates if needed
180
- if layout_coordinate_system == "polar":
181
- r, theta = offset
182
- offset = [r * np.cos(theta), r * np.sin(theta)]
183
-
184
- offsets.append(offset)
185
-
186
- if style.get("size") == "label":
187
- # NOTE: it's ok to overwrite the dict here
188
- style["size"] = _get_label_width_height(
189
- str(vertex_labels[vid]), **style.get("label", {})
190
- )
191
-
238
+ for i, (vid, row) in enumerate(self._layout.iterrows()):
192
239
  stylei = rotate_style(style, index=i, key=vid)
240
+ if stylei.get("size", 20) == "label":
241
+ stylei["size"] = _get_label_width_height(
242
+ str(self._labels[vid]), **style.get("label", {})
243
+ )
193
244
  if cmap_fun is not None:
194
245
  colorarray.append(style["facecolor"])
195
246
  stylei["facecolor"] = cmap_fun(stylei["facecolor"])
@@ -207,7 +258,7 @@ class VertexCollection(PatchCollection):
207
258
  kwargs["cmap"] = style["cmap"]
208
259
  kwargs["norm"] = norm
209
260
 
210
- return patches, offsets, sizes, kwargs
261
+ return patches, sizes, kwargs
211
262
 
212
263
  def _compute_label_collection(self):
213
264
  transform = self.get_offset_transform()
@@ -230,6 +281,12 @@ class VertexCollection(PatchCollection):
230
281
  )
231
282
 
232
283
  def get_labels(self):
284
+ """Get the vertex labels.
285
+
286
+ Returns:
287
+ The artist with the LabelCollection.
288
+ """
289
+
233
290
  if hasattr(self, "_label_collection"):
234
291
  return self._label_collection
235
292
  else:
@@ -245,6 +302,30 @@ class VertexCollection(PatchCollection):
245
302
  if val and hasattr(self, "stale_callback_post"):
246
303
  self.stale_callback_post(self)
247
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
+
248
329
  @mpl.artist.allow_rasterization
249
330
  def draw(self, renderer):
250
331
  if not self.get_visible():
@@ -257,6 +338,9 @@ class VertexCollection(PatchCollection):
257
338
 
258
339
  self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
259
340
 
341
+ # Set the label rotations already, hopefully this is not too early
342
+ self._update_children()
343
+
260
344
  # NOTE: This draws the vertices first, then the labels.
261
345
  # The correct order would be vertex1->label1->vertex2->label2, etc.
262
346
  # We might fix if we manage to find a way to do it.
@@ -266,10 +350,12 @@ class VertexCollection(PatchCollection):
266
350
 
267
351
 
268
352
  def make_patch(
269
- marker: str, size: float | Sequence[float], **kwargs
353
+ marker: str = "o",
354
+ size: float | Sequence[float] = 20,
355
+ **kwargs,
270
356
  ) -> tuple[Patch, float]:
271
357
  """Make a patch of the given marker shape and size."""
272
- forbidden_props = ["label", "cmap", "norm"]
358
+ forbidden_props = ["label", "cmap", "norm", "cascade"]
273
359
  for prop in forbidden_props:
274
360
  if prop in kwargs:
275
361
  kwargs.pop(prop)
@@ -280,7 +366,8 @@ def make_patch(
280
366
 
281
367
  # Size of vertices is determined in self._transforms, which scales with dpi, rather than here,
282
368
  # so normalise by the average dimension (btw x and y) to keep the ratio of the marker.
283
- # If you check in get_sizes, you will see that rescaling also happens with the max of width and height.
369
+ # If you check in get_sizes, you will see that rescaling also happens with the max of width
370
+ # and height.
284
371
  size = np.asarray(size, dtype=float)
285
372
  size_max = size.max()
286
373
  if size_max > 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iplotx
3
- Version: 0.2.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
@@ -11,12 +11,20 @@ Author-email: Fabio Zanini <fabio.zanini@unsw.edu.au>
11
11
  Maintainer-email: Fabio Zanini <fabio.zanini@unsw.edu.au>
12
12
  License: MIT
13
13
  Keywords: graph,network,plotting,visualisation
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Education
17
+ Classifier: Intended Audience :: Science/Research
14
18
  Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Natural Language :: English
15
20
  Classifier: Operating System :: OS Independent
16
21
  Classifier: Programming Language :: Python :: 3
17
22
  Classifier: Programming Language :: Python :: 3.11
18
23
  Classifier: Programming Language :: Python :: 3.12
19
24
  Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Scientific/Engineering :: Visualization
26
+ Classifier: Topic :: System :: Networking
27
+ Classifier: Typing :: Typed
20
28
  Requires-Python: >=3.11
21
29
  Requires-Dist: matplotlib>=2.0.0
22
30
  Requires-Dist: numpy>=2.0.0
@@ -31,11 +39,12 @@ Description-Content-Type: text/markdown
31
39
  ![Github Actions](https://github.com/fabilab/iplotx/actions/workflows/test.yml/badge.svg)
32
40
  ![PyPI - Version](https://img.shields.io/pypi/v/iplotx)
33
41
  ![RTD](https://readthedocs.org/projects/iplotx/badge/?version=latest)
42
+ ![pylint](assets/pylint.svg)
34
43
 
35
44
  # iplotx
36
45
  Plotting networks from igraph and networkx.
37
46
 
38
- **NOTE**: This is currently alpha quality software. The API and functionality will break constantly, so use at your own risk. That said, if you have things you would like to see improved, please open a GitHub issue.
47
+ **NOTE**: This is currently beta quality software. The API and functionality are settling in and might break occasionally.
39
48
 
40
49
  ## Installation
41
50
  ```bash
@@ -68,9 +77,13 @@ See [gallery](https://iplotx.readthedocs.io/en/latest/gallery/index.html).
68
77
  - Support storing the plot to disk thanks to the many matplotlib backends (SVG, PNG, PDF, etc.). ✅
69
78
  - Support flexible yet easy styling. ✅
70
79
  - Efficient plotting of large graphs using matplotlib's collection functionality. ✅
80
+ - Support editing plotting elements after the plot is created, e.g. changing node colors, labels, etc. ✅
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. ✅
71
83
  - Support trees from special libraries such as ete3, biopython, etc. This will need a dedicated function and layouting. ✅
72
- - Support animations, e.g. showing the evolution of a network over time. 🏗️
73
84
  - Support uni- and bi-directional communication between graph object and plot object.🏗️
74
85
 
86
+ **NOTE:** The last item can probably be achieved already by using `matplotlib`'s existing callback functionality. It is currently untested, but if you manage to get it to work on your graph let me know and I'll add it to the examples (with credit).
87
+
75
88
  ## Authors
76
89
  Fabio Zanini (https://fabilab.org)
@@ -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,,
@@ -1,30 +0,0 @@
1
- iplotx/__init__.py,sha256=DIOWUaEo9vgmz8B6hBlurV95BEYmjmUe5BU-jRWrtG4,418
2
- iplotx/groups.py,sha256=QZ4iounIcoPQfxRIJamGJljpdKTsVJGM0DCfBPJKBm0,6106
3
- iplotx/label.py,sha256=zS53rzA2j4yjo150CfjqbYfBTFWNG5tt9AkfdNWZlZ0,3884
4
- iplotx/layout.py,sha256=IDj66gXUvAN8q1uBIhmob-ZOQFWO8GAlW-4gGprN7H0,3963
5
- iplotx/network.py,sha256=VcrKdq0PzbXfufIAir-nyMgrpfGvvXpdqK5H8kPDf3I,9739
6
- iplotx/plotting.py,sha256=1C6wenDeUohoYVl_vAcTs2aR-5LcRHFOcNb4ghIbkLI,7152
7
- iplotx/style.py,sha256=m-sWbC4qjfdPr4fUns_vCPKl035-uJXLy6F4J-egQjM,11058
8
- iplotx/tree.py,sha256=7o9BUltkl37wy9Gn8RepPoNlcbzZYALC1tZVLDJ0CVo,9372
9
- iplotx/typing.py,sha256=dfiCkn_7plbQZ1tkBAdK8vCap8r9ZB6Skw2W4RYKVbo,1078
10
- iplotx/version.py,sha256=yw3yV4Zl2O7TK5Iy1aB2-9BhAM-EcoMbba7u7G2n1TE,66
11
- iplotx/vertex.py,sha256=h0B5pTDxT2dTd73fN8sXYsJdzanwaI38T12ZYpX_EgI,9505
12
- iplotx/edge/__init__.py,sha256=1BE4qCIXJwcN6A1AQVY1PFxje9WNYR1PEMk36vTALkk,30285
13
- iplotx/edge/arrow.py,sha256=dTlXMi1PsQDbpIktjRCGtubMGF72iOg1Gw9vC0FMOYo,11217
14
- iplotx/edge/ports.py,sha256=UdK3ylEWOyaZkKAKqN-reL0euF6QBWHLTJG7Ts7uhz4,1126
15
- iplotx/ingest/__init__.py,sha256=75Pml7X65tP8b2G3qaeZUdnDgwP6dclcCEEFl0BYSdo,4707
16
- iplotx/ingest/heuristics.py,sha256=32AZ8iidM_uooaBKe2EMjNU_nJDbhA_28ADc0dpQy5A,6636
17
- iplotx/ingest/typing.py,sha256=QEgCpLyfp-0v9czn6OaJ0_nuCo1AXv3lGS3aD6w-Ezw,3134
18
- iplotx/ingest/providers/network/igraph.py,sha256=_J7lH-jrT0_1oSfwgT_mQMRhxNoFwHo8dyBLCbgqETQ,2766
19
- iplotx/ingest/providers/network/networkx.py,sha256=u7NegapWZ0gWUj5n1PUVD-zZ92lKUiv6BLNTNIrXlRk,4233
20
- iplotx/ingest/providers/tree/biopython.py,sha256=7ZVD_WwIaBOSl-at4r_Y4d2qHQq6BcvlV-yDxMVCWnw,3456
21
- iplotx/ingest/providers/tree/cogent3.py,sha256=5O92zkdA43LWQ7h6r-uG_5X76EPHo-Yx9ODLb0hd_qc,3636
22
- iplotx/ingest/providers/tree/ete4.py,sha256=sGm2363yLJWsWsrRn2iFx8qRiq57h_5dQWBD41xJmhY,3660
23
- iplotx/ingest/providers/tree/skbio.py,sha256=iNZhY_TNLpzd55cK2nybjiftUfakeOrDDJ9dMBna_io,3629
24
- iplotx/utils/geometry.py,sha256=K5ZBYPmz4-KNm64pDh2p0L6PF5-u57SCVaEd2eWeRv0,8956
25
- iplotx/utils/internal.py,sha256=WWfcZDGK8Ut1y_tOHRGg9wSqY1bwSeLQO7dHM_8Tvwo,107
26
- iplotx/utils/matplotlib.py,sha256=T37SMwKNSA-dRKgNkVw5H4fGr5NACtxvBPebDJGdhjk,4516
27
- iplotx/utils/style.py,sha256=fEi14nc37HQqHxZTPeQaTnFFwbSneY1oDCyv2UbWfKk,22
28
- iplotx-0.2.0.dist-info/METADATA,sha256=5Xey_j3r4NJ78C9f-cMvfyhZc9rPb6E6xwdBXbVjRUI,3055
29
- iplotx-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
30
- iplotx-0.2.0.dist-info/RECORD,,
File without changes