iplotx 1.1.0__py3-none-any.whl → 1.2.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/__init__.py CHANGED
@@ -11,6 +11,7 @@ from .plotting import (
11
11
  network,
12
12
  graph,
13
13
  tree,
14
+ doubletree,
14
15
  plot,
15
16
  )
16
17
  import iplotx.artists as artists
@@ -20,6 +21,7 @@ import iplotx.style as style
20
21
  __all__ = [
21
22
  "network",
22
23
  "tree",
24
+ "doubletree",
23
25
  "plot",
24
26
  "graph",
25
27
  "artists",
iplotx/edge/__init__.py CHANGED
@@ -274,6 +274,12 @@ class EdgeCollection(mpl.collections.PatchCollection):
274
274
  """Return mappable for colorbar."""
275
275
  return self
276
276
 
277
+ def shift(self, x: float, y: float) -> None:
278
+ """Shift the cascade by a certain amount."""
279
+ for path in self._paths:
280
+ path.vertices[:, 0] += x
281
+ path.vertices[:, 1] += y
282
+
277
283
  def _get_adjacent_vertices_info(self):
278
284
  index = self._vertex_collection.get_index()
279
285
  index = pd.Series(
iplotx/edge/arrow.py CHANGED
@@ -2,7 +2,12 @@
2
2
  Module for edge arrows in iplotx.
3
3
  """
4
4
 
5
- from typing import Never, Optional
5
+ import sys
6
+ if sys.version_info < (3, 11):
7
+ from typing_extensions import Never
8
+ else:
9
+ from typing import Never
10
+ from typing import Optional
6
11
 
7
12
  from math import atan2, cos, sin
8
13
  import numpy as np
iplotx/ingest/__init__.py CHANGED
@@ -2,16 +2,24 @@
2
2
  This module focuses on how to ingest network/tree data into standard data structures no matter what library they come from.
3
3
  """
4
4
 
5
- import pathlib
6
- import pkgutil
7
- import importlib
8
- import warnings
5
+ import sys
9
6
  from typing import (
10
7
  Optional,
11
8
  Sequence,
12
- Protocol,
13
9
  )
10
+
11
+ # NOTE: __init__ in Protocols has had a difficult gestation
12
+ # https://github.com/python/cpython/issues/88970
13
+ if sys.version_info < (3, 11):
14
+ Protocol = object
15
+ else:
16
+ from typing import Protocol
17
+
14
18
  from collections.abc import Hashable
19
+ import pathlib
20
+ import pkgutil
21
+ import importlib
22
+ import warnings
15
23
  import pandas as pd
16
24
 
17
25
  from ..typing import (
@@ -35,6 +43,9 @@ provider_protocols = {
35
43
  data_providers: dict[str, dict[str, Protocol]] = {kind: {} for kind in provider_protocols}
36
44
  for kind in data_providers:
37
45
  providers_path = pathlib.Path(__file__).parent.joinpath("providers").joinpath(kind)
46
+ if sys.version_info < (3, 11):
47
+ providers_path = str(providers_path)
48
+
38
49
  for importer, module_name, _ in pkgutil.iter_modules([providers_path]):
39
50
  module = importlib.import_module(f"iplotx.ingest.providers.{kind}.{module_name}")
40
51
  for key, val in module.__dict__.items():
@@ -138,11 +149,11 @@ def ingest_tree_data(
138
149
 
139
150
 
140
151
  # INTERNAL FUNCTIONS
141
- def _update_data_providers(kind):
142
- """Update data provieders dynamically from external packages."""
152
+ def _update_data_providers(kind: str):
153
+ """Update data providers dynamically from external packages."""
143
154
  discovered_providers = importlib.metadata.entry_points(group=f"iplotx.{kind}_data_providers")
144
155
  for entry_point in discovered_providers:
145
- if entry_point.name not in data_providers["network"]:
156
+ if entry_point.name not in data_providers[kind]:
146
157
  try:
147
158
  data_providers[kind][entry_point.name] = entry_point.load()
148
159
  except Exception as e:
@@ -3,7 +3,6 @@ from typing import (
3
3
  Optional,
4
4
  Sequence,
5
5
  Iterable,
6
- Self,
7
6
  )
8
7
 
9
8
  from ...typing import (
@@ -19,12 +18,12 @@ class SimpleTree:
19
18
  branch_length: Length of the branch leading to this node/tree.
20
19
  """
21
20
 
22
- children: Sequence[Self] = []
21
+ children: Sequence = []
23
22
  branch_length: float = 1
24
23
  name: str = ""
25
24
 
26
25
  @classmethod
27
- def from_dict(cls, data: dict) -> Self:
26
+ def from_dict(cls, data: dict):
28
27
  """Create a SimpleTree from a dictionary.
29
28
 
30
29
  Parameters:
iplotx/ingest/typing.py CHANGED
@@ -5,15 +5,20 @@ Networkx and trees are treated separately for practical reasons: many tree analy
5
5
  work as well on general networks.
6
6
  """
7
7
 
8
+ import sys
8
9
  from typing import (
9
- NotRequired,
10
- TypedDict,
11
- Protocol,
12
10
  Optional,
13
11
  Sequence,
14
12
  Any,
15
13
  Iterable,
16
14
  )
15
+ # NOTE: __init__ in Protocols has had a difficult gestation
16
+ # https://github.com/python/cpython/issues/88970
17
+ if sys.version_info < (3, 11):
18
+ Protocol = object
19
+ else:
20
+ from typing import Protocol
21
+
17
22
  from collections.abc import Hashable
18
23
  import numpy as np
19
24
  import pandas as pd
@@ -26,6 +31,11 @@ from .heuristics import (
26
31
  normalise_tree_layout,
27
32
  )
28
33
 
34
+ if sys.version_info < (3, 11):
35
+ from typing_extensions import TypedDict, NotRequired
36
+ else:
37
+ from typing import TypedDict, NotRequired
38
+
29
39
 
30
40
  class NetworkData(TypedDict):
31
41
  """Network data structure for iplotx."""
@@ -1,7 +1,6 @@
1
1
  from typing import (
2
2
  Optional,
3
3
  Sequence,
4
- Self,
5
4
  )
6
5
  import numpy as np
7
6
  import pandas as pd
@@ -103,8 +102,8 @@ class NetworkArtist(mpl.artist.Artist):
103
102
  @classmethod
104
103
  def from_other(
105
104
  cls: "NetworkArtist", # NOTE: This is fixed in Python 3.14
106
- other: Self,
107
- ) -> Self:
105
+ other,
106
+ ):
108
107
  """Create a NetworkArtist as a copy of another one.
109
108
 
110
109
  Parameters:
@@ -123,7 +122,7 @@ class NetworkArtist(mpl.artist.Artist):
123
122
  def from_edgecollection(
124
123
  cls: "NetworkArtist", # NOTE: This is fixed in Python 3.14
125
124
  edge_collection: EdgeCollection | Edge3DCollection,
126
- ) -> Self:
125
+ ):
127
126
  """Create a NetworkArtist from iplotx artists.
128
127
 
129
128
  Parameters:
iplotx/plotting.py CHANGED
@@ -1,4 +1,8 @@
1
- from typing import Optional, Sequence
1
+ from typing import (
2
+ Optional,
3
+ Sequence,
4
+ Any,
5
+ )
2
6
  from contextlib import nullcontext
3
7
  import numpy as np
4
8
  import pandas as pd
@@ -269,8 +273,86 @@ def tree(
269
273
  return artist
270
274
 
271
275
 
276
+ def doubletree(
277
+ tree_left: Optional[TreeType] = None,
278
+ tree_right: Optional[TreeType] = None,
279
+ kwargs_left: Optional[dict[Any]] = None,
280
+ kwargs_right: Optional[dict[Any]] = None,
281
+ gap: float = 0,
282
+ ax: Optional[mpl.axes.Axes] = None,
283
+ title: Optional[str] = None,
284
+ aspect: Optional[str | float] = None,
285
+ margins: float | tuple[float, float] = 0,
286
+ strip_axes: bool = True,
287
+ figsize: Optional[tuple[float, float]] = None,
288
+ ) -> tuple[TreeArtist, TreeArtist]:
289
+ """Visualize two trees facing each other.
290
+
291
+ Parameters:
292
+ tree_left: The tree to plot on the left side.
293
+ tree_right: The tree to plot on the right side.
294
+ kwargs_left: Additional keyword arguments passed to the left tree plotting function.
295
+ kwargs_right: Additional keyword arguments passed to the right tree plotting function.
296
+ ax: The axis to plot on. If None, a new figure and axis will be created. Defaults to
297
+ None.
298
+ title: If not None, set the axes title to this value.
299
+ aspect: If not None, set the aspect ratio of the axis to this value. The most common
300
+ value is 1.0, which proportionates x- and y-axes.
301
+ margins: How much margin to leave around the plot. A higher value (e.g. 0.1) can be
302
+ used as a quick fix when some vertex shapes reach beyond the plot edge. This is
303
+ a fraction of the data limits, so 0.1 means 10% of the data limits will be left
304
+ as margin.
305
+ strip_axes: If True, remove axis spines and ticks.
306
+ figsize: If ax is None, a new matplotlib Figure is created. This argument specifies
307
+ the (width, height) dimension of the figure in inches. If ax is not None, this
308
+ argument is ignored. If None, the default matplotlib figure size is used.
309
+ Returns:
310
+ A tuple with the left and right TreeArtist objects.
311
+ """
312
+ artist1 = tree(
313
+ tree_left,
314
+ layout="horizontal",
315
+ layout_orientation="right",
316
+ ax=ax,
317
+ strip_axes=False,
318
+ figsize=figsize,
319
+ **kwargs_left or {},
320
+ )
321
+
322
+ ax = artist1.axes
323
+
324
+ if kwargs_right is None:
325
+ kwargs_right = {}
326
+
327
+ had_layout_start = "layout_start" in kwargs_right
328
+
329
+ artist2 = tree(
330
+ tree_right,
331
+ layout="horizontal",
332
+ layout_orientation="left",
333
+ ax=ax,
334
+ title=title,
335
+ aspect=aspect,
336
+ strip_axes=False,
337
+ margins=margins,
338
+ **kwargs_right,
339
+ )
340
+
341
+ if not had_layout_start:
342
+ x2min = artist2.get_layout().values[:, 0].min()
343
+ x1max = artist1.get_layout().values[:, 0].max()
344
+ xshift = x1max - x2min + gap
345
+
346
+ artist2.shift(0.5 * xshift, 0)
347
+ artist1.shift(-0.5 * xshift, 0)
348
+
349
+ _postprocess_axes(ax, [artist1, artist2], strip=strip_axes, ignore_previous=True)
350
+
351
+ return (artist1, artist2)
352
+
353
+
272
354
  # INTERNAL ROUTINES
273
- def _postprocess_axes(ax, artists, strip=True, had_data=None):
355
+ def _postprocess_axes(ax, artists, strip=True, had_data=None, ignore_previous=False):
274
356
  """Postprocess axis after plotting."""
275
357
 
276
358
  if strip:
@@ -309,7 +391,11 @@ def _postprocess_axes(ax, artists, strip=True, had_data=None):
309
391
  for art in artists:
310
392
  bboxes.append(art.get_datalim(ax.transData))
311
393
  bbox = mpl.transforms.Bbox.union(bboxes)
312
- ax.update_datalim(bbox)
394
+
395
+ if not ignore_previous:
396
+ ax.update_datalim(bbox)
397
+ else:
398
+ ax.dataLim.update_from_data_xy(bbox.corners(), ignore=True)
313
399
 
314
400
  # Autoscale for x/y axis limits
315
401
  ax.autoscale_view()
iplotx/tree/__init__.py CHANGED
@@ -219,6 +219,31 @@ class TreeArtist(mpl.artist.Artist):
219
219
  else:
220
220
  raise ValueError(f"Unknown layout kind: {kind}. Use 'vertex' or 'edge'.")
221
221
 
222
+ def shift(self, x: float, y: float) -> None:
223
+ """Shift layout coordinates for all tree elements.
224
+
225
+ Paramerers:
226
+ x: The shift in x direction.
227
+ y: The shift in y direction.
228
+ """
229
+ layout_columns = [f"_ipx_layout_{i}" for i in range(self._ipx_internal_data["ndim"])]
230
+ self._ipx_internal_data["vertex_df"][layout_columns[0]] += x
231
+ self._ipx_internal_data["vertex_df"][layout_columns[1]] += y
232
+
233
+ self.get_vertices()._layout.values[:, 0] += x
234
+ self.get_vertices()._layout.values[:, 1] += y
235
+ self.get_vertices()._update_offsets_from_layout()
236
+
237
+ self.get_edges().shift(x, y)
238
+
239
+ if hasattr(self, "_leaf_vertices"):
240
+ self.get_leaf_vertices()._layout.values[:, 0] += x
241
+ self.get_leaf_vertices()._layout.values[:, 1] += y
242
+ self.get_leaf_vertices()._update_offsets_from_layout()
243
+
244
+ if hasattr(self, "_cascades"):
245
+ self._cascades.shift(x, y)
246
+
222
247
  def get_datalim(self, transData, pad=0.15):
223
248
  """Get limits on x/y axes based on the graph layout data.
224
249
 
iplotx/tree/cascades.py CHANGED
@@ -158,6 +158,12 @@ class CascadeCollection(mpl.collections.PatchCollection):
158
158
  zorder=zorder,
159
159
  )
160
160
 
161
+ def shift(self, x: float, y: float) -> None:
162
+ """Shift the cascade by a certain amount."""
163
+ for path in self._paths:
164
+ path.vertices[:, 0] += x
165
+ path.vertices[:, 1] += y
166
+
161
167
  def get_maxdepth(self) -> float:
162
168
  """Get the maxdepth of the cascades.
163
169
 
iplotx/version.py CHANGED
@@ -2,4 +2,4 @@
2
2
  iplotx version information module.
3
3
  """
4
4
 
5
- __version__ = "1.1.0"
5
+ __version__ = "1.2.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iplotx
3
- Version: 1.1.0
3
+ Version: 1.2.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
@@ -26,8 +26,8 @@ Classifier: Programming Language :: Python :: 3.13
26
26
  Classifier: Topic :: Scientific/Engineering :: Visualization
27
27
  Classifier: Topic :: System :: Networking
28
28
  Classifier: Typing :: Typed
29
- Requires-Python: >=3.11
30
- Requires-Dist: matplotlib>=2.0.0
29
+ Requires-Python: >=3.10
30
+ Requires-Dist: matplotlib>=3.10.0
31
31
  Requires-Dist: numpy>=2.0.0
32
32
  Requires-Dist: pandas>=2.0.0
33
33
  Provides-Extra: igraph
@@ -78,8 +78,7 @@ import iplotx as ipx
78
78
 
79
79
  g = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)])
80
80
  layout = nx.layout.circular_layout(g)
81
- fig, ax = plt.subplots(figsize=(3, 3))
82
- ipx.plot(g, ax=ax, layout=layout)
81
+ ipx.plot(g, layout)
83
82
  ```
84
83
 
85
84
  ![Quick start image](/docs/source/_static/graph_basic.png)
@@ -1,23 +1,23 @@
1
- iplotx/__init__.py,sha256=RzSct91jO8abrxOIn33rKEnDUgYpu1oj4olbObgX_hs,489
1
+ iplotx/__init__.py,sha256=RKlRSSEAv2qECd6rCiovdLDu-4k1eXMGCOCPt0xwpFA,523
2
2
  iplotx/artists.py,sha256=2dBDT240zGwKb6tIc_y9pXeyU3LuYeF9wjj2tvi4KJo,730
3
3
  iplotx/label.py,sha256=7eS8ByadrhdIFOZz19U4VrS-oXY_ndFYNB-D4RZbFqI,9573
4
4
  iplotx/layout.py,sha256=S-iFxHaIOzhBDG2JUzl9_oDBRP5TYY1hXnEOs0h1Rck,5588
5
- iplotx/plotting.py,sha256=FvV33DCuEjJwO9ytiYJuQmfOywgF-cDANd6nEE5s8R0,13211
5
+ iplotx/plotting.py,sha256=RyAdvaHSpuyJkf8DF3SJBvEXBrPmJEdovUyAlBWQvqU,16228
6
6
  iplotx/typing.py,sha256=QLdzV358IiD1CFe88MVp0D77FSx5sSAVUmM_2WPPE8I,1463
7
- iplotx/version.py,sha256=mrnbNTnvhHXH5H10Drqi5HLqPXOwoyNlRYIAv7vTAy0,66
7
+ iplotx/version.py,sha256=2CsPuf8-y4qmmHpnn5_1DZwxypX8SRHYrsqTTmN1h5A,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
11
11
  iplotx/art3d/edge/arrow.py,sha256=14BFXY9kDOUGPZl2fMD9gRVGyaaN5kyd-l6ikBg6WHU,3601
12
12
  iplotx/art3d/edge/geometry.py,sha256=76VUmpPG-4Mls7x_994dMwdDPrWWnjT7nHJsHfwK_hA,2467
13
- iplotx/edge/__init__.py,sha256=lkrMkQFx9PNzorKc9trQ8MggC-nZSyALhvP78DMvhN4,26363
14
- iplotx/edge/arrow.py,sha256=U7vvBo7IMwo1qiyU9cyUEwraOaBcJLgdu9oU2OyoHL4,17453
13
+ iplotx/edge/__init__.py,sha256=GlY_CmlMVD1DWJeOb5cFQEP_4K1pPneS_fRLqAxNGMk,26573
14
+ iplotx/edge/arrow.py,sha256=ymup2YT_0GVYMtZw_DSKrZqFHG_ysYteEhmoL6T8Mu4,17563
15
15
  iplotx/edge/geometry.py,sha256=jkTMvQC5425GjB_fmGLIPJeSDAr_7NZF8zZDLTrSj34,15541
16
16
  iplotx/edge/leaf.py,sha256=SyGMv2PIOoH0pey8-aMVaZheK3hNe1Qz_okcyWbc4E4,4268
17
17
  iplotx/edge/ports.py,sha256=BpkbiEhX4mPBBAhOv4jcKFG4Y8hxXz5GRtVLCC0jbtI,1235
18
- iplotx/ingest/__init__.py,sha256=S0YfnXcFKseB7ZBQc4yRt0cNDsLlhqdom0TmSY3OY2E,4756
18
+ iplotx/ingest/__init__.py,sha256=k1Q-7lSdotMR4RkF1x0t19RFsTknohX0L507Dw69WyU,5035
19
19
  iplotx/ingest/heuristics.py,sha256=715VqgfKek5LOJnu1vTo7RqPgCl-Bb8Cf6o7_Tt57fA,5797
20
- iplotx/ingest/typing.py,sha256=61LwNwrTHVh8eqqC778Gr81zPYcUKW61mDgGCCsuGSk,14181
20
+ iplotx/ingest/typing.py,sha256=nk0UTqTuZoa9YE7F8RlOTqhxPw4OEYFTqrFFRWQs0jI,14488
21
21
  iplotx/ingest/providers/network/graph_tool.py,sha256=iTCf4zHe4Zmdd8Tlz6j7Xfo_FwfsIiK5JkQfH3uq7TM,3028
22
22
  iplotx/ingest/providers/network/igraph.py,sha256=WL9Yx2IF5QhUIoKMlozdyq5HWIZ-IJmNoeS8GOhL0KU,2945
23
23
  iplotx/ingest/providers/network/networkx.py,sha256=ehCg4npL073HX-eAG-VoP6refLPsMb3lYG51xt_rNjA,4636
@@ -26,20 +26,20 @@ iplotx/ingest/providers/tree/biopython.py,sha256=4N_54cVyHHPcASJZGr6pHKE2p5R3i8C
26
26
  iplotx/ingest/providers/tree/cogent3.py,sha256=JmELbDK7LyybiJzFNbmeqZ4ySJoDajvFfJebpNfFKWo,1073
27
27
  iplotx/ingest/providers/tree/dendropy.py,sha256=uRMe46PfDPUTeNInUO2Gbp4pVr-WIFIZQvrND2tovsg,1548
28
28
  iplotx/ingest/providers/tree/ete4.py,sha256=D7usSq0MOjzrk3EoLi834IlaDGwv7_qG6Qt0ptfKqfI,928
29
- iplotx/ingest/providers/tree/simple.py,sha256=aV9wGqBomJ5klM_aJQeuL_Q_J1pLCv6AFN98BPDiKUw,2593
29
+ iplotx/ingest/providers/tree/simple.py,sha256=-T7Kf-G4F4niggy_tNZ8AafDf8fpDdthC-vlkjbEZso,2569
30
30
  iplotx/ingest/providers/tree/skbio.py,sha256=O1KUr8tYi28pZ3VVjapgO4Uj-YpMuix3GhOH5je8Lv4,822
31
- iplotx/network/__init__.py,sha256=oEv6f8oFYrtcI_NKabr8a_oIWTc1jXXTl_yO1xox_rE,13575
31
+ iplotx/network/__init__.py,sha256=cJ6m6s157AOCqg-znUAlsumuZ2jiE9QsVQ3-GCK01wo,13543
32
32
  iplotx/network/groups.py,sha256=E_eYVXRHjv1DcyA4RupTkMa-rRFrIKkt9Rxn_Elw9Nc,6796
33
33
  iplotx/style/__init__.py,sha256=rf1GutrE8hHUhCoe4FGKYX-aNtHuu_U-kYQnqUxZNrY,10282
34
34
  iplotx/style/leaf_info.py,sha256=3xBn7xv9Uy2KAqdhM9S6ew5ZBJrGRTXRL3xXb8atfLw,1018
35
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
36
+ iplotx/tree/__init__.py,sha256=nV2iWrtpPhpGZnkEJqK8ydimMi7pJtRQ1Eqn5dgqUyA,31043
37
+ iplotx/tree/cascades.py,sha256=Wwqhy46QGeb4LNGUuz_-bgNWUMz6PFzs_dIxIb1dtqc,8394
38
38
  iplotx/tree/scalebar.py,sha256=Yxt_kF8JdTwKGa8Jzqt3qVePPK5ZBG8P0EiONrsh3E8,11863
39
39
  iplotx/utils/geometry.py,sha256=6RrC6qaB0-1vIk1LhGA4CfsiMd-9JNniSPyL_l9mshE,9245
40
40
  iplotx/utils/internal.py,sha256=WWfcZDGK8Ut1y_tOHRGg9wSqY1bwSeLQO7dHM_8Tvwo,107
41
41
  iplotx/utils/matplotlib.py,sha256=p_53Oamof0RI4mtV8HrdDtZbgVqUxeUZ_KDvLZSiBUQ,8604
42
42
  iplotx/utils/style.py,sha256=vyNP80nDYVinqm6_9ltCJCtjK35ZcGlHvOskNv3eQBc,4225
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,,
43
+ iplotx-1.2.1.dist-info/METADATA,sha256=DVxutHflrg2vXCJfyzmPR83ywc4_5DfGdrViUMvV43E,5001
44
+ iplotx-1.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ iplotx-1.2.1.dist-info/RECORD,,
File without changes