iplotx 0.12.0__py3-none-any.whl → 1.0.1__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/artists.py CHANGED
@@ -3,19 +3,21 @@ All artists defined in iplotx.
3
3
  """
4
4
 
5
5
  from .network import NetworkArtist
6
- from .tree import TreeArtist
6
+ from .network.groups import GroupingCollection
7
7
  from .vertex import VertexCollection
8
8
  from .edge import EdgeCollection
9
9
  from .label import LabelCollection
10
10
  from .edge.arrow import EdgeArrowCollection
11
11
  from .edge.leaf import LeafEdgeCollection
12
- from .cascades import CascadeCollection
13
12
  from .art3d.vertex import Vertex3DCollection
14
13
  from .art3d.edge import Edge3DCollection
14
+ from .tree import TreeArtist
15
+ from .tree.cascades import CascadeCollection
15
16
 
16
17
 
17
18
  ___all__ = (
18
19
  NetworkArtist,
20
+ GroupingCollection,
19
21
  TreeArtist,
20
22
  VertexCollection,
21
23
  EdgeCollection,
@@ -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()
@@ -7,33 +7,33 @@ import numpy as np
7
7
  import pandas as pd
8
8
  import matplotlib as mpl
9
9
 
10
- from .typing import (
10
+ from ..typing import (
11
11
  GraphType,
12
12
  LayoutType,
13
13
  )
14
- from .style import (
14
+ from ..style import (
15
15
  get_style,
16
16
  rotate_style,
17
17
  )
18
- from .utils.matplotlib import (
18
+ from ..utils.matplotlib import (
19
19
  _stale_wrapper,
20
20
  _forwarder,
21
21
  _build_cmap_fun,
22
22
  )
23
- from .ingest import (
23
+ from ..ingest import (
24
24
  ingest_network_data,
25
25
  )
26
- from .vertex import (
26
+ from ..vertex import (
27
27
  VertexCollection,
28
28
  )
29
- from .edge import (
29
+ from ..edge import (
30
30
  EdgeCollection,
31
31
  make_stub_patch as make_undirected_edge_patch,
32
32
  )
33
- from .art3d.vertex import (
33
+ from ..art3d.vertex import (
34
34
  vertex_collection_2d_to_3d,
35
35
  )
36
- from .art3d.edge import (
36
+ from ..art3d.edge import (
37
37
  Edge3DCollection,
38
38
  edge_collection_2d_to_3d,
39
39
  )
@@ -1,5 +1,5 @@
1
1
  """
2
- Module for vertex groupings code, especially the GroupingArtist class.
2
+ Module for vertex groupings code, especially the GroupingCollection class.
3
3
  """
4
4
 
5
5
  from typing import Union
@@ -9,22 +9,22 @@ import matplotlib as mpl
9
9
  from matplotlib.collections import PatchCollection
10
10
 
11
11
 
12
- from .typing import (
12
+ from ..typing import (
13
13
  GroupingType,
14
14
  LayoutType,
15
15
  )
16
- from .ingest.heuristics import (
16
+ from ..ingest.heuristics import (
17
17
  normalise_layout,
18
18
  normalise_grouping,
19
19
  )
20
- from .style import get_style, rotate_style
21
- from .utils.geometry import (
20
+ from ..style import get_style, rotate_style
21
+ from ..utils.geometry import (
22
22
  convex_hull,
23
23
  _compute_group_path_with_vertex_padding,
24
24
  )
25
25
 
26
26
 
27
- class GroupingArtist(PatchCollection):
27
+ class GroupingCollection(PatchCollection):
28
28
  """Matplotlib artist for a vertex grouping (clustering/cover).
29
29
 
30
30
  This class is used to plot patches surrounding groups of vertices in a network.
iplotx/plotting.py CHANGED
@@ -13,7 +13,7 @@ from .typing import (
13
13
  TreeType,
14
14
  )
15
15
  from .network import NetworkArtist
16
- from .groups import GroupingArtist
16
+ from .network.groups import GroupingCollection
17
17
  from .tree import TreeArtist
18
18
  from .style import context
19
19
 
@@ -101,7 +101,7 @@ def network(
101
101
  nwkart = None
102
102
 
103
103
  if grouping is not None:
104
- grpart = GroupingArtist(
104
+ grpart = GroupingCollection(
105
105
  grouping,
106
106
  layout,
107
107
  network=network,
@@ -10,38 +10,41 @@ import numpy as np
10
10
  import pandas as pd
11
11
  import matplotlib as mpl
12
12
 
13
- from .style import (
13
+ from ..style import (
14
14
  context,
15
15
  get_style,
16
16
  rotate_style,
17
17
  merge_styles,
18
18
  )
19
- from .utils.matplotlib import (
19
+ from ..utils.matplotlib import (
20
20
  _stale_wrapper,
21
21
  _forwarder,
22
22
  _build_cmap_fun,
23
23
  )
24
- from .ingest import (
24
+ from ..ingest import (
25
25
  ingest_tree_data,
26
26
  data_providers,
27
27
  )
28
- from .vertex import (
28
+ from ..vertex import (
29
29
  VertexCollection,
30
30
  )
31
- from .edge import (
31
+ from ..edge import (
32
32
  EdgeCollection,
33
33
  make_stub_patch as make_undirected_edge_patch,
34
34
  )
35
- from .edge.leaf import (
35
+ from ..edge.leaf import (
36
36
  LeafEdgeCollection,
37
37
  )
38
- from .label import (
38
+ from ..label import (
39
39
  LabelCollection,
40
40
  )
41
41
  from .cascades import (
42
42
  CascadeCollection,
43
43
  )
44
- from .network import (
44
+ from .scalebar import (
45
+ TreeScalebarArtist,
46
+ )
47
+ from ..network import (
45
48
  _update_from_internal,
46
49
  )
47
50
 
@@ -663,6 +666,43 @@ class TreeArtist(mpl.artist.Artist):
663
666
  """Get the orientation of the tree layout."""
664
667
  return self._ipx_internal_data.get("orientation", None)
665
668
 
669
+ def scalebar(
670
+ self,
671
+ loc: str = "upper left",
672
+ label_format: str = ".2f",
673
+ **kwargs,
674
+ ):
675
+ """Create scalebar for the tree.
676
+
677
+ Parameters:
678
+ legth: Length of the scalebar in data units.
679
+ loc: Location of the scalebar. Same options as `matplotlib.legend`.
680
+ kwargs: Additional keyword arguments passed to `TreeScalebarArtist`. These are
681
+ generally the same options that you would pass to a legend, such as
682
+ bbox_to_anchor, bbox_transform, etc.
683
+ Returns:
684
+ The artist with the tree scale bar.
685
+ """
686
+ if self.axes is None:
687
+ raise RuntimeError("Cannot add a scalebar if the artist is not in an Axes.")
688
+
689
+ scalebar = TreeScalebarArtist(
690
+ self,
691
+ layout=self.get_layout_name(),
692
+ loc=loc,
693
+ label_format=label_format,
694
+ **kwargs,
695
+ )
696
+
697
+ # Remove previous scalebars if any
698
+ for art in self.axes._children:
699
+ if isinstance(art, TreeScalebarArtist) and art._treeartist == self:
700
+ art.remove()
701
+
702
+ self.axes.add_artist(scalebar)
703
+
704
+ return scalebar
705
+
666
706
  def style_subtree(
667
707
  self,
668
708
  nodes: Sequence[Hashable],
@@ -5,16 +5,15 @@ from typing import (
5
5
  import warnings
6
6
  import numpy as np
7
7
  import pandas as pd
8
+ import matplotlib as mpl
8
9
 
9
- from .typing import (
10
+ from ..typing import (
10
11
  TreeType,
11
12
  )
12
- from .ingest.typing import (
13
+ from ..ingest.typing import (
13
14
  TreeDataProvider,
14
15
  )
15
- import matplotlib as mpl
16
-
17
- from .style import (
16
+ from ..style import (
18
17
  copy_with_deep_values,
19
18
  rotate_style,
20
19
  )
@@ -0,0 +1,327 @@
1
+ from typing import (
2
+ Any,
3
+ )
4
+ import numpy as np
5
+ from matplotlib import (
6
+ _api,
7
+ )
8
+ from matplotlib import collections as mcoll
9
+ from matplotlib.legend import Legend
10
+ from matplotlib.legend_handler import HandlerErrorbar
11
+ from matplotlib.lines import Line2D
12
+ from matplotlib.offsetbox import (
13
+ HPacker,
14
+ VPacker,
15
+ DrawingArea,
16
+ TextArea,
17
+ )
18
+
19
+
20
+ def _update_prop(legend_artist, orig_handle):
21
+ # NOTE: This is de facto a bug in mpl, because Line2D.set_linestyle()
22
+ # does two things: it reformats tuple-style dashing, and it sets the
23
+ # artist as stale. We want to do the former only here, so we reset
24
+ # the artist as the original stale state after calling it.
25
+ stale_orig = legend_artist.stale
26
+ legend_artist.set_linestyle(orig_handle.get_linestyle()[0])
27
+ legend_artist.stale = stale_orig
28
+
29
+ # These other properties can be set directly.
30
+ legend_artist._linewidth = orig_handle.get_linewidth()[0]
31
+ legend_artist._color = orig_handle.get_edgecolor()[0]
32
+ legend_artist._gapcolor = orig_handle._gapcolor
33
+
34
+
35
+ class TreeScalebarArtist(Legend):
36
+ def __init__(
37
+ self,
38
+ treeartist,
39
+ layout: str = "horizontal",
40
+ frameon: bool = False,
41
+ label_format: str = ".2f",
42
+ **kwargs,
43
+ ):
44
+ handles = [treeartist.get_edges()]
45
+ labels = [""]
46
+ self._layout = layout
47
+ self._treeartist = treeartist
48
+ self._label_format = label_format
49
+
50
+ if layout == "vertical":
51
+ handler_kwargs = dict(xerr_size=0, yerr_size=1)
52
+ else:
53
+ handler_kwargs = dict(xerr_size=1)
54
+ handler = TreeLegendHandler(
55
+ update_func=_update_prop,
56
+ **handler_kwargs,
57
+ )
58
+
59
+ super().__init__(
60
+ treeartist.axes,
61
+ handles,
62
+ labels,
63
+ handler_map={handles[0]: handler},
64
+ frameon=frameon,
65
+ **kwargs,
66
+ )
67
+
68
+ def _init_legend_box(self, handles, labels, markerfirst=True):
69
+ """
70
+ Create the legend box.
71
+
72
+ This is a modified version of the original Legend._init_legend_box
73
+ method to accommodate a scale bar.
74
+ """
75
+
76
+ fontsize = self._fontsize
77
+
78
+ # legend_box is a HPacker, horizontally packed with columns.
79
+ # Each column is a VPacker, vertically packed with legend items.
80
+ # Each legend item is a HPacker packed with:
81
+ # - handlebox: a DrawingArea which contains the legend handle.
82
+ # - labelbox: a TextArea which contains the legend text.
83
+
84
+ text_list = [] # the list of text instances
85
+ handle_list = [] # the list of handle instances
86
+ handles_and_labels = []
87
+
88
+ # The approximate height and descent of text. These values are
89
+ # only used for plotting the legend handle.
90
+ descent = 0.35 * fontsize * (self.handleheight - 0.7) # heuristic.
91
+ height = fontsize * self.handleheight - descent
92
+ # each handle needs to be drawn inside a box of (x, y, w, h) =
93
+ # (0, -descent, width, height). And their coordinates should
94
+ # be given in the display coordinates.
95
+
96
+ # The transformation of each handle will be automatically set
97
+ # to self.get_transform(). If the artist does not use its
98
+ # default transform (e.g., Collections), you need to
99
+ # manually set their transform to the self.get_transform().
100
+ legend_handler_map = self.get_legend_handler_map()
101
+
102
+ for orig_handle, label in zip(handles, labels):
103
+ handler = self.get_legend_handler(legend_handler_map, orig_handle)
104
+ if handler is None:
105
+ _api.warn_external(
106
+ "Legend does not support handles for "
107
+ f"{type(orig_handle).__name__} "
108
+ "instances.\nA proxy artist may be used "
109
+ "instead.\nSee: https://matplotlib.org/"
110
+ "stable/users/explain/axes/legend_guide.html"
111
+ "#controlling-the-legend-entries"
112
+ )
113
+ # No handle for this artist, so we just defer to None.
114
+ handle_list.append(None)
115
+ else:
116
+ handlebox = DrawingArea(
117
+ width=self.handlelength * fontsize,
118
+ height=height,
119
+ xdescent=0.0,
120
+ ydescent=descent,
121
+ )
122
+ # Create the artist for the legend which represents the
123
+ # original artist/handle.
124
+ handle_list.append(handler.legend_artist(self, orig_handle, fontsize, handlebox))
125
+
126
+ # The scale bar line is in this handle
127
+ bar_handle = handle_list[-1]
128
+ label = self._get_label_from_bar_handle(bar_handle)
129
+
130
+ textbox = TextArea(
131
+ label,
132
+ multilinebaseline=True,
133
+ textprops=dict(
134
+ verticalalignment="baseline",
135
+ horizontalalignment="left",
136
+ fontproperties=self.prop,
137
+ ),
138
+ )
139
+ text_list.append(textbox._text)
140
+
141
+ handles_and_labels.append((handlebox, textbox))
142
+
143
+ columnbox = []
144
+ # array_split splits n handles_and_labels into ncols columns, with the
145
+ # first n%ncols columns having an extra entry. filter(len, ...)
146
+ # handles the case where n < ncols: the last ncols-n columns are empty
147
+ # and get filtered out.
148
+ for handles_and_labels_column in filter(
149
+ len, np.array_split(handles_and_labels, self._ncols)
150
+ ):
151
+ # pack handlebox and labelbox into itembox
152
+ if self._layout == "vertical":
153
+ itempacker = HPacker
154
+ else:
155
+ itempacker = VPacker
156
+ itemboxes = [
157
+ itempacker(
158
+ pad=0,
159
+ sep=self.handletextpad * fontsize,
160
+ children=[h, t] if markerfirst else [t, h],
161
+ align="center",
162
+ )
163
+ for h, t in handles_and_labels_column
164
+ ]
165
+ # pack columnbox
166
+ alignment = "baseline" if markerfirst else "right"
167
+ columnbox.append(
168
+ VPacker(
169
+ pad=0, sep=self.labelspacing * fontsize, align=alignment, children=itemboxes
170
+ )
171
+ )
172
+
173
+ mode = "expand" if self._mode == "expand" else "fixed"
174
+ sep = self.columnspacing * fontsize
175
+ self._legend_handle_box = HPacker(
176
+ pad=0, sep=sep, align="baseline", mode=mode, children=columnbox
177
+ )
178
+ self._legend_title_box = TextArea("")
179
+ self._legend_box = VPacker(
180
+ pad=self.borderpad * fontsize,
181
+ sep=self.labelspacing * fontsize,
182
+ align=self._alignment,
183
+ children=[self._legend_title_box, self._legend_handle_box],
184
+ )
185
+ self._legend_box.set_figure(self.get_figure(root=False))
186
+ self._legend_box.axes = self.axes
187
+ self.texts = text_list
188
+ self.legend_handles = handle_list
189
+
190
+ def _get_label_from_bar_handle(self, bar_handle: Any) -> str:
191
+ # Extract the x coordinates of the scale bar
192
+ p0, p1 = bar_handle.get_segments()[0]
193
+
194
+ bar_trans = bar_handle.get_transform()
195
+ data_trans = self.parent.transData
196
+ composite_trans = data_trans.inverted() + bar_trans
197
+
198
+ p0_data = composite_trans.transform(p0)
199
+ p1_data = composite_trans.transform(p1)
200
+ distance = np.linalg.norm(p1_data - p0_data)
201
+ label = format(distance, self._label_format)
202
+ return label
203
+
204
+ def draw(self, renderer):
205
+ bar_handle = self.legend_handles[0]
206
+ label = self._get_label_from_bar_handle(bar_handle)
207
+
208
+ text_handle = (
209
+ self._legend_box.get_children()[1].get_children()[0].get_children()[0].get_children()[1]
210
+ )
211
+ # Bypass stale=True (we are already redrawing)
212
+ text_handle.set_text(label)
213
+
214
+ super().draw(renderer)
215
+
216
+
217
+ class TreeLegendHandler(HandlerErrorbar):
218
+ def __init__(self, marker_size=6, **kw):
219
+ self.marker_size = marker_size
220
+ super().__init__(**kw)
221
+
222
+ def create_artists(
223
+ self,
224
+ legend,
225
+ orig_handle,
226
+ xdescent,
227
+ ydescent,
228
+ width,
229
+ height,
230
+ fontsize,
231
+ trans,
232
+ ):
233
+ # docstring inherited
234
+ plotline = orig_handle
235
+
236
+ xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, width, height, fontsize)
237
+ ydata = np.full_like(xdata, (height - ydescent) / 2)
238
+
239
+ xdata_marker = np.asarray(xdata_marker)
240
+ ydata_marker = np.asarray(ydata[: len(xdata_marker)])
241
+
242
+ xerr_size, yerr_size = self.get_err_size(
243
+ legend, xdescent, ydescent, width, height, fontsize
244
+ )
245
+
246
+ if legend._layout == "vertical":
247
+ xdata, ydata = np.array(
248
+ [
249
+ ((x, y - yerr_size), (x, y + yerr_size))
250
+ for x, y in zip(xdata_marker, ydata_marker)
251
+ ]
252
+ ).T
253
+
254
+ legline = Line2D(xdata, ydata)
255
+
256
+ legline_marker = Line2D(xdata_marker, ydata_marker)
257
+
258
+ # when plotlines are None (only errorbars are drawn), we just
259
+ # make legline invisible.
260
+ if plotline is None:
261
+ legline.set_visible(False)
262
+ legline_marker.set_visible(False)
263
+ else:
264
+ self.update_prop(legline, plotline, legend)
265
+
266
+ legline.set_drawstyle("default")
267
+ legline.set_marker("none")
268
+
269
+ self.update_prop(legline_marker, plotline, legend)
270
+ legline_marker.set_linestyle("None")
271
+
272
+ if legend.markerscale != 1:
273
+ newsz = legline_marker.get_markersize() * legend.markerscale
274
+ legline_marker.set_markersize(newsz)
275
+
276
+ handle_barlinecols = []
277
+ handle_caplines = []
278
+
279
+ if legend._layout != "vertical":
280
+ verts = [
281
+ ((x - xerr_size, y), (x + xerr_size, y)) for x, y in zip(xdata_marker, ydata_marker)
282
+ ]
283
+ coll = mcoll.LineCollection(verts)
284
+ self.update_prop(coll, plotline, legend)
285
+ handle_barlinecols.append(coll)
286
+
287
+ # Always show the cap lines
288
+ if True:
289
+ capline_left = Line2D(xdata_marker - xerr_size, ydata_marker)
290
+ capline_right = Line2D(xdata_marker + xerr_size, ydata_marker)
291
+ self.update_prop(capline_left, plotline, legend)
292
+ self.update_prop(capline_right, plotline, legend)
293
+ capline_left.set_marker("|")
294
+ capline_right.set_marker("|")
295
+
296
+ handle_caplines.append(capline_left)
297
+ handle_caplines.append(capline_right)
298
+
299
+ else:
300
+ verts = [
301
+ ((x, y - yerr_size), (x, y + yerr_size)) for x, y in zip(xdata_marker, ydata_marker)
302
+ ]
303
+ coll = mcoll.LineCollection(verts)
304
+ self.update_prop(coll, plotline, legend)
305
+ handle_barlinecols.append(coll)
306
+
307
+ # Always show the cap lines
308
+ if True:
309
+ capline_left = Line2D(xdata_marker, ydata_marker - yerr_size)
310
+ capline_right = Line2D(xdata_marker, ydata_marker + yerr_size)
311
+ self.update_prop(capline_left, plotline, legend)
312
+ self.update_prop(capline_right, plotline, legend)
313
+ capline_left.set_marker("_")
314
+ capline_right.set_marker("_")
315
+
316
+ handle_caplines.append(capline_left)
317
+ handle_caplines.append(capline_right)
318
+
319
+ artists = [
320
+ *handle_barlinecols,
321
+ *handle_caplines,
322
+ legline,
323
+ legline_marker,
324
+ ]
325
+ for artist in artists:
326
+ artist.set_transform(trans)
327
+ return artists
iplotx/version.py CHANGED
@@ -2,4 +2,4 @@
2
2
  iplotx version information module.
3
3
  """
4
4
 
5
- __version__ = "0.12.0"
5
+ __version__ = "1.0.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iplotx
3
- Version: 0.12.0
3
+ Version: 1.0.1
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,14 +1,10 @@
1
1
  iplotx/__init__.py,sha256=RzSct91jO8abrxOIn33rKEnDUgYpu1oj4olbObgX_hs,489
2
- iplotx/artists.py,sha256=XNtRwuvQdKkZCAejILydLD3J5B87sg5xPXuZFv_Gkk8,654
3
- iplotx/cascades.py,sha256=OPqF7Huls-HFmDA5MCF6DEZlUeRVaXsbQcHBoKAgNJs,8182
4
- iplotx/groups.py,sha256=g6ahm61BSBmd2weIjr40MvPi_GcNRgvNb9YklQsiza4,6784
2
+ iplotx/artists.py,sha256=2dBDT240zGwKb6tIc_y9pXeyU3LuYeF9wjj2tvi4KJo,730
5
3
  iplotx/label.py,sha256=7eS8ByadrhdIFOZz19U4VrS-oXY_ndFYNB-D4RZbFqI,9573
6
4
  iplotx/layout.py,sha256=KxmRLqjo8AYCBAmXez8rIiLU2sM34qhb6ox9AHYwRyE,4839
7
- iplotx/network.py,sha256=mhR1hwlLrATYJfCZUgk7sfmtw3yEjAE0TtE0b-1N8hE,13567
8
- iplotx/plotting.py,sha256=icEefWJnS2lEGLp4t1LhDSP40JuvNKgOie3FDLOnTMk,13195
9
- iplotx/tree.py,sha256=TxbNoBHS0CfswrcMIWCNtnOl_3e4-PwCrVo0goywC0U,28807
5
+ iplotx/plotting.py,sha256=FvV33DCuEjJwO9ytiYJuQmfOywgF-cDANd6nEE5s8R0,13211
10
6
  iplotx/typing.py,sha256=QLdzV358IiD1CFe88MVp0D77FSx5sSAVUmM_2WPPE8I,1463
11
- iplotx/version.py,sha256=OAoDyBg_N0NwrLolwb2vnkue-AwN9sTDzRmDcTNfAog,67
7
+ iplotx/version.py,sha256=V7W0rXi0DhtuwovCU4hLuXqTV7XgZp0IPed9QF9xUso,66
12
8
  iplotx/vertex.py,sha256=_yYyvusn4vYvi6RBEW6CHa3vnbv43GnZylnMIaK4bG0,16040
13
9
  iplotx/art3d/vertex.py,sha256=Xf8Um30X2doCd8KdNN7332F6BxC4k72Mb_GeRAuzQfQ,2545
14
10
  iplotx/art3d/edge/__init__.py,sha256=uw1U_mMXqcZAvea-7JbU1PUKULQD1CMMrbwY02tiWRQ,8529
@@ -22,6 +18,7 @@ iplotx/edge/ports.py,sha256=BpkbiEhX4mPBBAhOv4jcKFG4Y8hxXz5GRtVLCC0jbtI,1235
22
18
  iplotx/ingest/__init__.py,sha256=S0YfnXcFKseB7ZBQc4yRt0cNDsLlhqdom0TmSY3OY2E,4756
23
19
  iplotx/ingest/heuristics.py,sha256=715VqgfKek5LOJnu1vTo7RqPgCl-Bb8Cf6o7_Tt57fA,5797
24
20
  iplotx/ingest/typing.py,sha256=61LwNwrTHVh8eqqC778Gr81zPYcUKW61mDgGCCsuGSk,14181
21
+ iplotx/ingest/providers/network/graph_tool.py,sha256=iTCf4zHe4Zmdd8Tlz6j7Xfo_FwfsIiK5JkQfH3uq7TM,3028
25
22
  iplotx/ingest/providers/network/igraph.py,sha256=WL9Yx2IF5QhUIoKMlozdyq5HWIZ-IJmNoeS8GOhL0KU,2945
26
23
  iplotx/ingest/providers/network/networkx.py,sha256=ehCg4npL073HX-eAG-VoP6refLPsMb3lYG51xt_rNjA,4636
27
24
  iplotx/ingest/providers/network/simple.py,sha256=e_aHhiHhN9DrMoNrt7tEMPURXGhQ1TYRPzsxDEptUlc,3766
@@ -31,13 +28,18 @@ iplotx/ingest/providers/tree/dendropy.py,sha256=uRMe46PfDPUTeNInUO2Gbp4pVr-WIFIZ
31
28
  iplotx/ingest/providers/tree/ete4.py,sha256=D7usSq0MOjzrk3EoLi834IlaDGwv7_qG6Qt0ptfKqfI,928
32
29
  iplotx/ingest/providers/tree/simple.py,sha256=aV9wGqBomJ5klM_aJQeuL_Q_J1pLCv6AFN98BPDiKUw,2593
33
30
  iplotx/ingest/providers/tree/skbio.py,sha256=O1KUr8tYi28pZ3VVjapgO4Uj-YpMuix3GhOH5je8Lv4,822
31
+ iplotx/network/__init__.py,sha256=oEv6f8oFYrtcI_NKabr8a_oIWTc1jXXTl_yO1xox_rE,13575
32
+ iplotx/network/groups.py,sha256=E_eYVXRHjv1DcyA4RupTkMa-rRFrIKkt9Rxn_Elw9Nc,6796
34
33
  iplotx/style/__init__.py,sha256=rf1GutrE8hHUhCoe4FGKYX-aNtHuu_U-kYQnqUxZNrY,10282
35
34
  iplotx/style/leaf_info.py,sha256=3xBn7xv9Uy2KAqdhM9S6ew5ZBJrGRTXRL3xXb8atfLw,1018
36
35
  iplotx/style/library.py,sha256=58Y8BlllGLsR4pQM7_PVCP5tH6_4GkchXZvJpqGHlcg,8534
36
+ iplotx/tree/__init__.py,sha256=mFCgXSusB1dSMc-xN_zOc5PojpEf_XyP8yR3xeZVQMY,30065
37
+ iplotx/tree/cascades.py,sha256=on5GyqbWasl1zgK7bYXYQE0LOSfHc1z-1hnm0GWd6aw,8184
38
+ iplotx/tree/scalebar.py,sha256=Yxt_kF8JdTwKGa8Jzqt3qVePPK5ZBG8P0EiONrsh3E8,11863
37
39
  iplotx/utils/geometry.py,sha256=6RrC6qaB0-1vIk1LhGA4CfsiMd-9JNniSPyL_l9mshE,9245
38
40
  iplotx/utils/internal.py,sha256=WWfcZDGK8Ut1y_tOHRGg9wSqY1bwSeLQO7dHM_8Tvwo,107
39
41
  iplotx/utils/matplotlib.py,sha256=p_53Oamof0RI4mtV8HrdDtZbgVqUxeUZ_KDvLZSiBUQ,8604
40
42
  iplotx/utils/style.py,sha256=vyNP80nDYVinqm6_9ltCJCtjK35ZcGlHvOskNv3eQBc,4225
41
- iplotx-0.12.0.dist-info/METADATA,sha256=OcfxvA_JL2GOXYH7_67iDL1N4_U3fDm7--3nXbUHKU8,5087
42
- iplotx-0.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
43
- iplotx-0.12.0.dist-info/RECORD,,
43
+ iplotx-1.0.1.dist-info/METADATA,sha256=jxmMX1X7reQl_O0RJ1GtOS1kT0p8Nkkwoa7rTEwX3QQ,5053
44
+ iplotx-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ iplotx-1.0.1.dist-info/RECORD,,