iplotx 1.0.0__py3-none-any.whl → 1.1.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.
@@ -0,0 +1,100 @@
1
+ from typing import (
2
+ Optional,
3
+ Sequence,
4
+ )
5
+ from collections.abc import Hashable
6
+ import importlib
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from ....typing import (
11
+ LayoutType,
12
+ )
13
+ from ...heuristics import (
14
+ normalise_layout,
15
+ )
16
+ from ...typing import (
17
+ NetworkDataProvider,
18
+ NetworkData,
19
+ )
20
+ from ....utils.internal import (
21
+ _make_layout_columns,
22
+ )
23
+
24
+
25
+ class GraphToolDataProvider(NetworkDataProvider):
26
+ def __call__(
27
+ self,
28
+ layout: Optional[LayoutType] = None,
29
+ vertex_labels: Optional[Sequence[str] | dict[Hashable, str] | pd.Series] = None,
30
+ edge_labels: Optional[Sequence[str] | dict[str]] = None,
31
+ ) -> NetworkData:
32
+ """Create network data object for iplotx from an igraph object."""
33
+
34
+ # Get layout
35
+ vertex_df = normalise_layout(
36
+ layout,
37
+ network=self.network,
38
+ nvertices=self.number_of_vertices(),
39
+ )
40
+ ndim = vertex_df.shape[1]
41
+ vertex_df.columns = _make_layout_columns(ndim)
42
+
43
+ # Vertices are ordered integers, no gaps
44
+
45
+ # Vertex labels
46
+ # Recast vertex_labels=False as vertex_labels=None
47
+ if np.isscalar(vertex_labels) and (not vertex_labels):
48
+ vertex_labels = None
49
+ if vertex_labels is not None:
50
+ if np.isscalar(vertex_labels):
51
+ vertex_df["label"] = vertex_df.index.astype(str)
52
+ elif len(vertex_labels) != len(vertex_df):
53
+ raise ValueError("Vertex labels must be the same length as the number of vertices.")
54
+ else:
55
+ vertex_df["label"] = vertex_labels
56
+
57
+ # Edges are a list of tuples, because of multiedges
58
+ tmp = []
59
+ for edge in self.network.edges():
60
+ row = {"_ipx_source": edge.source(), "_ipx_target": edge.target()}
61
+ # TODO: add graph-tool edge attributes
62
+ # row.update(edge.attributes())
63
+ tmp.append(row)
64
+ if len(tmp):
65
+ edge_df = pd.DataFrame(tmp)
66
+ else:
67
+ edge_df = pd.DataFrame(columns=["_ipx_source", "_ipx_target"])
68
+ del tmp
69
+
70
+ # Edge labels
71
+ if edge_labels is not None:
72
+ if len(edge_labels) != len(edge_df):
73
+ raise ValueError("Edge labels must be the same length as the number of edges.")
74
+ edge_df["label"] = edge_labels
75
+
76
+ network_data = {
77
+ "vertex_df": vertex_df,
78
+ "edge_df": edge_df,
79
+ "directed": self.is_directed(),
80
+ "ndim": ndim,
81
+ }
82
+ return network_data
83
+
84
+ @staticmethod
85
+ def check_dependencies() -> bool:
86
+ return importlib.util.find_spec("graph_tool") is not None
87
+
88
+ @staticmethod
89
+ def graph_type():
90
+ import graph_tool.all as gt
91
+
92
+ return gt.Graph
93
+
94
+ def is_directed(self):
95
+ """Whether the network is directed."""
96
+ return self.network.is_directed()
97
+
98
+ def number_of_vertices(self):
99
+ """The number of vertices/nodes in the network."""
100
+ return self.network.num_vertices()
iplotx/layout.py CHANGED
@@ -2,7 +2,10 @@
2
2
  Layout functions, currently limited to trees.
3
3
  """
4
4
 
5
- from typing import Any
5
+ from typing import (
6
+ Any,
7
+ Optional,
8
+ )
6
9
  from collections.abc import (
7
10
  Hashable,
8
11
  Callable,
@@ -99,6 +102,8 @@ def _horizontal_tree_layout_right(
99
102
 
100
103
  def _horizontal_tree_layout(
101
104
  orientation="right",
105
+ start: tuple[float, float] = (0, 0),
106
+ span: Optional[float] = None,
102
107
  **kwargs,
103
108
  ) -> dict[Hashable, list[float]]:
104
109
  """Horizontal tree layout."""
@@ -110,11 +115,24 @@ def _horizontal_tree_layout(
110
115
  if orientation == "left":
111
116
  for key in layout:
112
117
  layout[key][0] *= -1
118
+
119
+ if span is not None:
120
+ cur_span = len(layout) - 1
121
+ for key in layout:
122
+ layout[key][1] = float(layout[key][1]) * span / cur_span
123
+
124
+ if start != (0, 0):
125
+ for key in layout:
126
+ layout[key][0] += start[0]
127
+ layout[key][1] += start[1]
128
+
113
129
  return layout
114
130
 
115
131
 
116
132
  def _vertical_tree_layout(
117
133
  orientation="descending",
134
+ start: tuple[float, float] = (0, 0),
135
+ span: Optional[float] = None,
118
136
  **kwargs,
119
137
  ) -> dict[Hashable, list[float]]:
120
138
  """Vertical tree layout."""
@@ -125,6 +143,17 @@ def _vertical_tree_layout(
125
143
  layout[key] = value[::-1]
126
144
  # Orient vertically
127
145
  layout[key][1] *= sign
146
+
147
+ if span is not None:
148
+ cur_span = len(layout) - 1
149
+ for key in layout:
150
+ layout[key][0] = float(layout[key][0]) * span / cur_span
151
+
152
+ if start != (0, 0):
153
+ for key in layout:
154
+ layout[key][0] += start[0]
155
+ layout[key][1] += start[1]
156
+
128
157
  return layout
129
158
 
130
159
 
iplotx/tree/__init__.py CHANGED
@@ -669,6 +669,7 @@ class TreeArtist(mpl.artist.Artist):
669
669
  def scalebar(
670
670
  self,
671
671
  loc: str = "upper left",
672
+ label_format: str = ".2f",
672
673
  **kwargs,
673
674
  ):
674
675
  """Create scalebar for the tree.
@@ -689,6 +690,8 @@ class TreeArtist(mpl.artist.Artist):
689
690
  self,
690
691
  layout=self.get_layout_name(),
691
692
  loc=loc,
693
+ label_format=label_format,
694
+ **kwargs,
692
695
  )
693
696
 
694
697
  # Remove previous scalebars if any
iplotx/tree/scalebar.py CHANGED
@@ -37,13 +37,15 @@ class TreeScalebarArtist(Legend):
37
37
  self,
38
38
  treeartist,
39
39
  layout: str = "horizontal",
40
- frameon=False,
40
+ frameon: bool = False,
41
+ label_format: str = ".2f",
41
42
  **kwargs,
42
43
  ):
43
44
  handles = [treeartist.get_edges()]
44
45
  labels = [""]
45
46
  self._layout = layout
46
47
  self._treeartist = treeartist
48
+ self._label_format = label_format
47
49
 
48
50
  if layout == "vertical":
49
51
  handler_kwargs = dict(xerr_size=0, yerr_size=1)
@@ -191,13 +193,12 @@ class TreeScalebarArtist(Legend):
191
193
 
192
194
  bar_trans = bar_handle.get_transform()
193
195
  data_trans = self.parent.transData
194
- # FIXME: this is off, probably because of some Packer anchor additional transform
195
196
  composite_trans = data_trans.inverted() + bar_trans
196
197
 
197
198
  p0_data = composite_trans.transform(p0)
198
199
  p1_data = composite_trans.transform(p1)
199
200
  distance = np.linalg.norm(p1_data - p0_data)
200
- label = f"{distance:.2f}"
201
+ label = format(distance, self._label_format)
201
202
  return label
202
203
 
203
204
  def draw(self, renderer):
iplotx/version.py CHANGED
@@ -2,4 +2,4 @@
2
2
  iplotx version information module.
3
3
  """
4
4
 
5
- __version__ = "1.0.0"
5
+ __version__ = "1.1.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iplotx
3
- Version: 1.0.0
3
+ Version: 1.1.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
@@ -53,14 +53,15 @@ Supports:
53
53
  - **networks**:
54
54
  - [networkx](https://networkx.org/)
55
55
  - [igraph](igraph.readthedocs.io/)
56
- - [minimal network data structure](https://iplotx.readthedocs.io/en/latest/gallery/plot_simplenetworkdataprovider.html#sphx-glr-gallery-plot-simplenetworkdataprovider-py) (zero dependency)
56
+ - [graph-tool](https://graph-tool.skewed.de/)
57
+ - [zero-dependency](https://iplotx.readthedocs.io/en/latest/gallery/plot_simplenetworkdataprovider.html#sphx-glr-gallery-plot-simplenetworkdataprovider-py)
57
58
  - **trees**:
58
59
  - [ETE4](https://etetoolkit.github.io/ete/)
59
60
  - [cogent3](https://cogent3.org/)
60
61
  - [Biopython](https://biopython.org/)
61
62
  - [scikit-bio](https://scikit.bio)
62
63
  - [dendropy](https://jeetsukumaran.github.io/DendroPy/index.html)
63
- - [minimal tree data structure](https://iplotx.readthedocs.io/en/latest/gallery/tree/plot_simpletreedataprovider.html#sphx-glr-gallery-tree-plot-simpletreedataprovider-py) (zero dependency)
64
+ - [zero-dependency](https://iplotx.readthedocs.io/en/latest/gallery/tree/plot_simpletreedataprovider.html#sphx-glr-gallery-tree-plot-simpletreedataprovider-py)
64
65
 
65
66
  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.
66
67
 
@@ -81,7 +82,7 @@ fig, ax = plt.subplots(figsize=(3, 3))
81
82
  ipx.plot(g, ax=ax, layout=layout)
82
83
  ```
83
84
 
84
- ![Quick start image](docs/source/_static/graph_basic.png)
85
+ ![Quick start image](/docs/source/_static/graph_basic.png)
85
86
 
86
87
  ## Documentation
87
88
  See [readthedocs](https://iplotx.readthedocs.io/en/latest/) for the full documentation.
@@ -90,11 +91,11 @@ See [readthedocs](https://iplotx.readthedocs.io/en/latest/) for the full documen
90
91
  See [gallery](https://iplotx.readthedocs.io/en/latest/gallery/index.html).
91
92
 
92
93
  ## Features
93
- - Plot networks from multiple libraries including networkx and igraph, using matplotlib as a backend. ✅
94
+ - Plot networks from multiple libraries including networkx, igraph and graph-tool, using Matplotlib. ✅
94
95
  - Plot trees from multiple libraries such as cogent3, ETE4, skbio, biopython, and dendropy. ✅
95
96
  - Flexible yet easy styling, including an internal library of styles ✅
96
97
  - Interactive plotting, e.g. zooming and panning after the plot is created. ✅
97
- - Store the plot to disk thanks to the many matplotlib backends (SVG, PNG, PDF, etc.). ✅
98
+ - Store the plot to disk in many formats (SVG, PNG, PDF, GIF, etc.). ✅
98
99
  - 3D network visualisation with depth shading. ✅
99
100
  - Efficient plotting of large graphs (up to ~1 million nodes on a laptop). ✅
100
101
  - Edit plotting elements after the plot is created, e.g. changing node colors, labels, etc. ✅
@@ -1,10 +1,10 @@
1
1
  iplotx/__init__.py,sha256=RzSct91jO8abrxOIn33rKEnDUgYpu1oj4olbObgX_hs,489
2
2
  iplotx/artists.py,sha256=2dBDT240zGwKb6tIc_y9pXeyU3LuYeF9wjj2tvi4KJo,730
3
3
  iplotx/label.py,sha256=7eS8ByadrhdIFOZz19U4VrS-oXY_ndFYNB-D4RZbFqI,9573
4
- iplotx/layout.py,sha256=KxmRLqjo8AYCBAmXez8rIiLU2sM34qhb6ox9AHYwRyE,4839
4
+ iplotx/layout.py,sha256=S-iFxHaIOzhBDG2JUzl9_oDBRP5TYY1hXnEOs0h1Rck,5588
5
5
  iplotx/plotting.py,sha256=FvV33DCuEjJwO9ytiYJuQmfOywgF-cDANd6nEE5s8R0,13211
6
6
  iplotx/typing.py,sha256=QLdzV358IiD1CFe88MVp0D77FSx5sSAVUmM_2WPPE8I,1463
7
- iplotx/version.py,sha256=Zw6LAvjlzbItG1QBPJb1Tuqkb2PVUYsDwCVRYEJusgc,66
7
+ iplotx/version.py,sha256=mrnbNTnvhHXH5H10Drqi5HLqPXOwoyNlRYIAv7vTAy0,66
8
8
  iplotx/vertex.py,sha256=_yYyvusn4vYvi6RBEW6CHa3vnbv43GnZylnMIaK4bG0,16040
9
9
  iplotx/art3d/vertex.py,sha256=Xf8Um30X2doCd8KdNN7332F6BxC4k72Mb_GeRAuzQfQ,2545
10
10
  iplotx/art3d/edge/__init__.py,sha256=uw1U_mMXqcZAvea-7JbU1PUKULQD1CMMrbwY02tiWRQ,8529
@@ -18,6 +18,7 @@ iplotx/edge/ports.py,sha256=BpkbiEhX4mPBBAhOv4jcKFG4Y8hxXz5GRtVLCC0jbtI,1235
18
18
  iplotx/ingest/__init__.py,sha256=S0YfnXcFKseB7ZBQc4yRt0cNDsLlhqdom0TmSY3OY2E,4756
19
19
  iplotx/ingest/heuristics.py,sha256=715VqgfKek5LOJnu1vTo7RqPgCl-Bb8Cf6o7_Tt57fA,5797
20
20
  iplotx/ingest/typing.py,sha256=61LwNwrTHVh8eqqC778Gr81zPYcUKW61mDgGCCsuGSk,14181
21
+ iplotx/ingest/providers/network/graph_tool.py,sha256=iTCf4zHe4Zmdd8Tlz6j7Xfo_FwfsIiK5JkQfH3uq7TM,3028
21
22
  iplotx/ingest/providers/network/igraph.py,sha256=WL9Yx2IF5QhUIoKMlozdyq5HWIZ-IJmNoeS8GOhL0KU,2945
22
23
  iplotx/ingest/providers/network/networkx.py,sha256=ehCg4npL073HX-eAG-VoP6refLPsMb3lYG51xt_rNjA,4636
23
24
  iplotx/ingest/providers/network/simple.py,sha256=e_aHhiHhN9DrMoNrt7tEMPURXGhQ1TYRPzsxDEptUlc,3766
@@ -32,13 +33,13 @@ iplotx/network/groups.py,sha256=E_eYVXRHjv1DcyA4RupTkMa-rRFrIKkt9Rxn_Elw9Nc,6796
32
33
  iplotx/style/__init__.py,sha256=rf1GutrE8hHUhCoe4FGKYX-aNtHuu_U-kYQnqUxZNrY,10282
33
34
  iplotx/style/leaf_info.py,sha256=3xBn7xv9Uy2KAqdhM9S6ew5ZBJrGRTXRL3xXb8atfLw,1018
34
35
  iplotx/style/library.py,sha256=58Y8BlllGLsR4pQM7_PVCP5tH6_4GkchXZvJpqGHlcg,8534
35
- iplotx/tree/__init__.py,sha256=6a8cbTd-OS-x8GEZJeRo3vneVjI98AiQW_gIo1H7h3Y,29969
36
+ iplotx/tree/__init__.py,sha256=mFCgXSusB1dSMc-xN_zOc5PojpEf_XyP8yR3xeZVQMY,30065
36
37
  iplotx/tree/cascades.py,sha256=on5GyqbWasl1zgK7bYXYQE0LOSfHc1z-1hnm0GWd6aw,8184
37
- iplotx/tree/scalebar.py,sha256=QC2l-Nx39g8fkO1tHHvbrtzLncfQc9L57BQniSk2h5Q,11849
38
+ iplotx/tree/scalebar.py,sha256=Yxt_kF8JdTwKGa8Jzqt3qVePPK5ZBG8P0EiONrsh3E8,11863
38
39
  iplotx/utils/geometry.py,sha256=6RrC6qaB0-1vIk1LhGA4CfsiMd-9JNniSPyL_l9mshE,9245
39
40
  iplotx/utils/internal.py,sha256=WWfcZDGK8Ut1y_tOHRGg9wSqY1bwSeLQO7dHM_8Tvwo,107
40
41
  iplotx/utils/matplotlib.py,sha256=p_53Oamof0RI4mtV8HrdDtZbgVqUxeUZ_KDvLZSiBUQ,8604
41
42
  iplotx/utils/style.py,sha256=vyNP80nDYVinqm6_9ltCJCtjK35ZcGlHvOskNv3eQBc,4225
42
- iplotx-1.0.0.dist-info/METADATA,sha256=VsUs2_FiPrGNcfAfnL-T0fB9RObJCdAUvjaEUO46vFg,5086
43
- iplotx-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
44
- iplotx-1.0.0.dist-info/RECORD,,
43
+ iplotx-1.1.0.dist-info/METADATA,sha256=BBWbFLjqgs00VaB3DXNSex39oA0iDplDgBeCmrO6hKw,5053
44
+ iplotx-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ iplotx-1.1.0.dist-info/RECORD,,
File without changes