iplotx 0.10.0__py3-none-any.whl → 0.11.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.
@@ -0,0 +1,147 @@
1
+ """
2
+ Module containing code to manipulate edge visualisations in 3D, especially the Edge3DCollection class.
3
+ """
4
+
5
+ from mpl_toolkits.mplot3d import Axes3D
6
+ from mpl_toolkits.mplot3d.art3d import (
7
+ Line3DCollection,
8
+ )
9
+
10
+ from ...utils.matplotlib import (
11
+ _forwarder,
12
+ )
13
+ from ...edge import (
14
+ EdgeCollection,
15
+ )
16
+ from .arrow import (
17
+ arrow_collection_2d_to_3d,
18
+ )
19
+ from .geometry import (
20
+ _compute_edge_segments as _compute_single_edge_segments,
21
+ )
22
+
23
+
24
+ @_forwarder(
25
+ (
26
+ "set_clip_path",
27
+ "set_clip_box",
28
+ "set_snap",
29
+ "set_sketch_params",
30
+ "set_animated",
31
+ "set_picker",
32
+ )
33
+ )
34
+ class Edge3DCollection(Line3DCollection):
35
+ """Collection of vertex patches for plotting."""
36
+
37
+ def get_children(self) -> tuple:
38
+ children = []
39
+ if hasattr(self, "_subedges"):
40
+ children.append(self._subedges)
41
+ if hasattr(self, "_arrows"):
42
+ children.append(self._arrows)
43
+ if hasattr(self, "_label_collection"):
44
+ children.append(self._label_collection)
45
+ return tuple(children)
46
+
47
+ def set_figure(self, fig) -> None:
48
+ super().set_figure(fig)
49
+ for child in self.get_children():
50
+ child.set_figure(fig)
51
+
52
+ @property
53
+ def axes(self):
54
+ return Line3DCollection.axes.__get__(self)
55
+
56
+ @axes.setter
57
+ def axes(self, new_axes):
58
+ Line3DCollection.axes.__set__(self, new_axes)
59
+ for child in self.get_children():
60
+ child.axes = new_axes
61
+
62
+ _get_adjacent_vertices_info = EdgeCollection._get_adjacent_vertices_info
63
+
64
+ def _compute_edge_segments(self):
65
+ """Compute the edge segments for all edges."""
66
+ vinfo = self._get_adjacent_vertices_info()
67
+
68
+ segments3d = []
69
+ for vcoord_data in vinfo["offsets"]:
70
+ segment = _compute_single_edge_segments(
71
+ vcoord_data,
72
+ )
73
+ segments3d.append(segment)
74
+ self.set_segments(segments3d)
75
+
76
+ def _update_before_draw(self) -> None:
77
+ """Update the collection before drawing."""
78
+ if isinstance(self.axes, Axes3D) and hasattr(self, "do_3d_projection"):
79
+ self.do_3d_projection()
80
+
81
+ # TODO: Here's where we would shorten the edges to fit the vertex
82
+ # projections from 3D onto 2D, if we wanted to do that. Because edges
83
+ # in 3D are chains of segments rathen than splines, the shortening
84
+ # needs to be done in a different way to how it's done in 2D.
85
+
86
+ def draw(self, renderer) -> None:
87
+ """Draw the collection of vertices in 3D.
88
+
89
+ Parameters:
90
+ renderer: The renderer to use for drawing.
91
+ """
92
+ # Prepare the collection for drawing
93
+ self._update_before_draw()
94
+
95
+ # Render the Line3DCollection
96
+ # NOTE: we are NOT calling EdgeCollection.draw here
97
+ super().draw(renderer)
98
+
99
+ # This sets the labels offsets
100
+ # TODO: implement labels in 3D (one could copy the function from 2D,
101
+ # but would also need to promote the 2D labels into 3D labels similarly to
102
+ # how it's done for 3D vertices).
103
+ # self._update_labels()
104
+
105
+ # Now attempt to draw the arrows
106
+ for child in self.get_children():
107
+ child.draw(renderer)
108
+
109
+
110
+ def edge_collection_2d_to_3d(
111
+ col: EdgeCollection,
112
+ zdir: str = "z",
113
+ axlim_clip: bool = False,
114
+ ):
115
+ """Convert a 2D EdgeCollection to a 3D Edge3DCollection.
116
+
117
+ Parameters:
118
+ col: The 2D EdgeCollection to convert.
119
+ zs: The z coordinate(s) to use for the 3D vertices.
120
+ zdir: The axis to use as the z axis (default is "z").
121
+ depthshade: Whether to apply depth shading (default is True).
122
+ axlim_clip: Whether to clip the vertices to the axes limits (default is False).
123
+ """
124
+ if not isinstance(col, EdgeCollection):
125
+ raise TypeError("vertices must be a VertexCollection")
126
+
127
+ # NOTE: after this line, none of the EdgeCollection methods will work
128
+ # It's become a static drawer now. It uses segments instead of paths.
129
+ col.__class__ = Edge3DCollection
130
+ col._compute_edge_segments()
131
+
132
+ col._axlim_clip = axlim_clip
133
+
134
+ # Convert the arrow collection if present
135
+ if hasattr(col, "_arrows"):
136
+ segments3d = col._segments3d
137
+
138
+ # Fix the x and y to the center of the target vertex (for now)
139
+ col._arrows._offsets[:] = [segment[-1][:2] for segment in segments3d]
140
+ zs = [segment[-1][2] for segment in segments3d]
141
+ arrow_collection_2d_to_3d(
142
+ col._arrows,
143
+ zs=zs,
144
+ zdir=zdir,
145
+ depthshade=False,
146
+ axlim_clip=axlim_clip,
147
+ )
@@ -0,0 +1,115 @@
1
+ """
2
+ Module containing code to manipulate arrow visualisations in 3D, especially the EdgeArrow3DCollection class.
3
+ """
4
+
5
+ from typing import (
6
+ Sequence,
7
+ )
8
+ from math import atan2, cos, sin
9
+ import numpy as np
10
+ from matplotlib import (
11
+ cbook,
12
+ )
13
+ from mpl_toolkits.mplot3d import Axes3D
14
+ from mpl_toolkits.mplot3d.art3d import (
15
+ Path3DCollection,
16
+ )
17
+
18
+ from ...utils.matplotlib import (
19
+ _forwarder,
20
+ )
21
+ from ...edge.arrow import (
22
+ EdgeArrowCollection,
23
+ )
24
+
25
+
26
+ @_forwarder(
27
+ (
28
+ "set_clip_path",
29
+ "set_clip_box",
30
+ "set_snap",
31
+ "set_sketch_params",
32
+ "set_animated",
33
+ "set_picker",
34
+ )
35
+ )
36
+ class EdgeArrow3DCollection(EdgeArrowCollection, Path3DCollection):
37
+ """Collection of vertex patches for plotting."""
38
+
39
+ def _update_before_draw(self) -> None:
40
+ """Update the collection before drawing."""
41
+ if (
42
+ isinstance(self.axes, Axes3D)
43
+ and hasattr(self, "do_3d_projection")
44
+ and (self.axes.M is not None)
45
+ ):
46
+ self.do_3d_projection()
47
+
48
+ # The original EdgeArrowCollection method for
49
+ # _update_before_draw cannot be used because it
50
+ # relies on paths, whereas edges are now a
51
+ # Line3DCollection which uses segments.
52
+ self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
53
+
54
+ if (not hasattr(self, "_z_markers_idx")) or (
55
+ not isinstance(self._z_markers_idx, np.ndarray)
56
+ ):
57
+ return
58
+
59
+ trans = self.get_offset_transform().transform
60
+
61
+ # The do_3d_projection method above reorders the
62
+ # arrow offsets in some way, so we might have to figure out
63
+ # what edge index corres
64
+ for i, ie in enumerate(self._z_markers_idx):
65
+ segments_2d = self._edge_collection.get_segments()[ie]
66
+
67
+ # We could reset the 3d projection here, might be a way to
68
+ # skip the function call above.
69
+ v2 = trans(segments_2d[-1])
70
+ v1 = trans(segments_2d[-2])
71
+ dv = v2 - v1
72
+ theta = atan2(*(dv[::-1]))
73
+ theta_old = self._angles[i]
74
+ dtheta = theta - theta_old
75
+ mrot = np.array([[cos(dtheta), sin(dtheta)], [-sin(dtheta), cos(dtheta)]])
76
+
77
+ apath = self._paths[i]
78
+ apath.vertices = apath.vertices @ mrot
79
+ self._angles[i] = theta
80
+
81
+ def draw(self, renderer) -> None:
82
+ """Draw the collection of vertices in 3D.
83
+
84
+ Parameters:
85
+ renderer: The renderer to use for drawing.
86
+ """
87
+ with self._use_zordered_offset():
88
+ with cbook._setattr_cm(self, _in_draw=True):
89
+ EdgeArrowCollection.draw(self, renderer)
90
+
91
+
92
+ def arrow_collection_2d_to_3d(
93
+ col: EdgeArrowCollection,
94
+ zs: np.ndarray | float | Sequence[float] = 0,
95
+ zdir: str = "z",
96
+ depthshade: bool = True,
97
+ axlim_clip: bool = False,
98
+ ):
99
+ """Convert a 2D EdgeArrowCollection to a 3D EdgeArrow3DCollection.
100
+
101
+ Parameters:
102
+ col: The 2D EdgeArrowCollection to convert.
103
+ zs: The z coordinate(s) to use for the 3D vertices.
104
+ zdir: The axis to use as the z axis (default is "z").
105
+ depthshade: Whether to apply depth shading (default is True).
106
+ axlim_clip: Whether to clip the vertices to the axes limits (default is False).
107
+ """
108
+ if not isinstance(col, EdgeArrowCollection):
109
+ raise TypeError("vertices must be a EdgeArrowCollection")
110
+
111
+ col.__class__ = EdgeArrow3DCollection
112
+ col._offset_zordered = None
113
+ col._depthshade = depthshade
114
+ col._in_draw = False
115
+ col.set_3d_properties(zs, zdir, axlim_clip)
@@ -7,19 +7,14 @@ from typing import (
7
7
  Sequence,
8
8
  )
9
9
  import numpy as np
10
- import matplotlib as mpl
11
10
 
12
- from ..typing import (
11
+ from ...typing import (
13
12
  Pair,
14
13
  )
15
14
 
16
15
 
17
- def _compute_edge_path_straight(
16
+ def _compute_edge_segments_straight(
18
17
  vcoord_data,
19
- vpath_fig,
20
- vsize_fig,
21
- trans,
22
- trans_inv,
23
18
  layout_coordinate_system: str = "cartesian",
24
19
  shrink: float = 0,
25
20
  **kwargs,
@@ -45,37 +40,11 @@ def _compute_edge_path_straight(
45
40
  f"Layout coordinate system not supported for straight edges in 3D: {layout_coordinate_system}.",
46
41
  )
47
42
 
48
- vcoord_data_cart = vcoord_data
43
+ segments = [vcoord_data[0], vcoord_data[1]]
44
+ return segments
49
45
 
50
- # Coordinates in figure (default) coords
51
- vcoord_fig = trans(vcoord_data_cart)
52
46
 
53
- points = []
54
-
55
- # Angles of the straight line
56
- # FIXME: In 2D, this is only used to make space for loops
57
- # let's ignore for now
58
- # theta = atan2(*((vcoord_fig[1] - vcoord_fig[0])[::-1]))
59
- theta = 0
60
-
61
- # TODO: Shorten at starting vertex (?)
62
- vs = vcoord_fig[0]
63
- points.append(vs)
64
-
65
- # TODO: Shorten at end vertex (?)
66
- ve = vcoord_fig[1]
67
- points.append(ve)
68
-
69
- codes = ["MOVETO", "LINETO"]
70
- path = mpl.path.Path(
71
- points,
72
- codes=[getattr(mpl.path.Path, x) for x in codes],
73
- )
74
- path.vertices = trans_inv(path.vertices)
75
- return path, (theta, theta + np.pi)
76
-
77
-
78
- def _compute_edge_path_3d(
47
+ def _compute_edge_segments(
79
48
  *args,
80
49
  tension: float = 0,
81
50
  waypoints: str | tuple[float, float] | Sequence[tuple[float, float]] | np.ndarray = "none",
@@ -98,7 +67,7 @@ def _compute_edge_path_3d(
98
67
  # )
99
68
 
100
69
  if np.isscalar(tension) and (tension == 0):
101
- return _compute_edge_path_straight(
70
+ return _compute_edge_segments_straight(
102
71
  *args,
103
72
  layout_coordinate_system=layout_coordinate_system,
104
73
  **kwargs,
iplotx/art3d/vertex.py CHANGED
@@ -9,7 +9,11 @@ import numpy as np
9
9
  from matplotlib import (
10
10
  cbook,
11
11
  )
12
- from mpl_toolkits.mplot3d.art3d import Path3DCollection
12
+ from mpl_toolkits.mplot3d import Axes3D
13
+ from mpl_toolkits.mplot3d.art3d import (
14
+ Path3DCollection,
15
+ text_2d_to_3d,
16
+ )
13
17
 
14
18
  from ..utils.matplotlib import (
15
19
  _forwarder,
@@ -32,6 +36,14 @@ from ..vertex import (
32
36
  class Vertex3DCollection(VertexCollection, Path3DCollection):
33
37
  """Collection of vertex patches for plotting."""
34
38
 
39
+ def _update_before_draw(self) -> None:
40
+ """Update the collection before drawing."""
41
+ # Set the sizes according to the current figure dpi
42
+ VertexCollection._update_before_draw(self)
43
+
44
+ if isinstance(self.axes, Axes3D) and hasattr(self, "do_3d_projection"):
45
+ self.do_3d_projection()
46
+
35
47
  def draw(self, renderer) -> None:
36
48
  """Draw the collection of vertices in 3D.
37
49
 
@@ -56,7 +68,7 @@ def vertex_collection_2d_to_3d(
56
68
  col: The 2D VertexCollection to convert.
57
69
  zs: The z coordinate(s) to use for the 3D vertices.
58
70
  zdir: The axis to use as the z axis (default is "z").
59
- depthshade: Whether to apply depth shading (default is True).
71
+ depthshade: Whether to aply depth shading (default is True).
60
72
  axlim_clip: Whether to clip the vertices to the axes limits (default is False).
61
73
  """
62
74
  if not isinstance(col, VertexCollection):
@@ -67,3 +79,9 @@ def vertex_collection_2d_to_3d(
67
79
  col._depthshade = depthshade
68
80
  col._in_draw = False
69
81
  col.set_3d_properties(zs, zdir, axlim_clip)
82
+
83
+ # Labels if present
84
+ if col.get_labels() is not None:
85
+ for z, art in zip(zs, col.get_labels()._labelartists):
86
+ # zdir=None means the text is always horizontal facing the camera
87
+ text_2d_to_3d(art, z, zdir=None, axlim_clip=axlim_clip)
iplotx/edge/__init__.py CHANGED
@@ -9,7 +9,7 @@ from typing import (
9
9
  Optional,
10
10
  Any,
11
11
  )
12
- from math import atan2, cos, pi, sin
12
+ from math import pi
13
13
  from collections import defaultdict
14
14
  import numpy as np
15
15
  import pandas as pd
@@ -181,17 +181,23 @@ class EdgeCollection(mpl.collections.PatchCollection):
181
181
 
182
182
  def set_figure(self, fig) -> None:
183
183
  super().set_figure(fig)
184
- self._update_paths()
184
+ self._update_before_draw()
185
185
  # NOTE: This sets the correct offsets in the arrows,
186
186
  # but not the correct sizes (see below)
187
- self._update_children()
187
+ self._update_labels()
188
188
  for child in self.get_children():
189
189
  # NOTE: This sets the sizes with correct dpi scaling in the arrows
190
190
  child.set_figure(fig)
191
191
 
192
- def _update_children(self):
193
- self._update_arrows()
194
- self._update_labels()
192
+ @property
193
+ def axes(self):
194
+ return mpl.artist.Artist.axes.__get__(self)
195
+
196
+ @axes.setter
197
+ def axes(self, new_axes):
198
+ mpl.artist.Artist.axes.__set__(self, new_axes)
199
+ for child in self.get_children():
200
+ child.axes = new_axes
195
201
 
196
202
  def set_transform(self, transform: mpl.transforms.Transform) -> None:
197
203
  """Set the transform for the edges and their children."""
@@ -303,7 +309,7 @@ class EdgeCollection(mpl.collections.PatchCollection):
303
309
  "sizes": vsizes,
304
310
  }
305
311
 
306
- def _update_paths(self, transform=None):
312
+ def _update_before_draw(self, transform=None):
307
313
  """Compute paths for the edges.
308
314
 
309
315
  Loops split the largest wedge left open by other
@@ -512,42 +518,6 @@ class EdgeCollection(mpl.collections.PatchCollection):
512
518
  if not style.get("rotate", True):
513
519
  self._label_collection.set_rotations(rotations)
514
520
 
515
- def _update_arrows(
516
- self,
517
- ) -> None:
518
- """Extract the start and/or end angles of the paths to compute arrows.
519
-
520
- Parameters:
521
- which: Which end of the edge to put an arrow on. Currently only "end" is accepted.
522
-
523
- NOTE: This function does *not* update the arrow sizes/_transforms to the correct dpi
524
- scaling. That's ok since the correct dpi scaling is set whenever there is a different
525
- figure (before first draw) and whenever a draw is called.
526
- """
527
- if not hasattr(self, "_arrows"):
528
- return
529
-
530
- transform = self.get_transform()
531
- trans = transform.transform
532
-
533
- for i, epath in enumerate(self.get_paths()):
534
- # Offset the arrow to point to the end of the edge
535
- self._arrows._offsets[i] = epath.vertices[-1]
536
-
537
- # Rotate the arrow to point in the direction of the edge
538
- apath = self._arrows._paths[i]
539
- # NOTE: because the tip of the arrow is at (0, 0) in patch space,
540
- # in theory it will rotate around that point already
541
- v2 = trans(epath.vertices[-1])
542
- v1 = trans(epath.vertices[-2])
543
- dv = v2 - v1
544
- theta = atan2(*(dv[::-1]))
545
- theta_old = self._arrows._angles[i]
546
- dtheta = theta - theta_old
547
- mrot = np.array([[cos(dtheta), sin(dtheta)], [-sin(dtheta), cos(dtheta)]])
548
- apath.vertices = apath.vertices @ mrot
549
- self._arrows._angles[i] = theta
550
-
551
521
  @_stale_wrapper
552
522
  def draw(self, renderer):
553
523
  # Visibility affects the children too
@@ -555,11 +525,15 @@ class EdgeCollection(mpl.collections.PatchCollection):
555
525
  return
556
526
 
557
527
  # This includes the subedges if present
558
- self._update_paths()
559
- # This sets the arrow offsets
560
- self._update_children()
528
+ self._update_before_draw()
561
529
 
530
+ # Now you can draw the edges
562
531
  super().draw(renderer)
532
+
533
+ # This sets the labels offsets
534
+ self._update_labels()
535
+
536
+ # Now you can draw arrows and labels
563
537
  for child in self.get_children():
564
538
  child.draw(renderer)
565
539
 
iplotx/edge/arrow.py CHANGED
@@ -4,6 +4,7 @@ Module for edge arrows in iplotx.
4
4
 
5
5
  from typing import Never, Optional
6
6
 
7
+ from math import atan2, cos, sin
7
8
  import numpy as np
8
9
  import matplotlib as mpl
9
10
  from matplotlib.patches import PathPatch
@@ -91,9 +92,7 @@ class EdgeArrowCollection(mpl.collections.PatchCollection):
91
92
  def set_figure(self, fig) -> None:
92
93
  """Set the figure for this artist and all children."""
93
94
  super().set_figure(fig)
94
- self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
95
- for child in self.get_children():
96
- child.set_figure(fig)
95
+ self._update_before_draw()
97
96
 
98
97
  def get_offset_transform(self):
99
98
  """Get offset transform for the edge arrows. This sets the tip of each arrow."""
@@ -126,6 +125,30 @@ class EdgeArrowCollection(mpl.collections.PatchCollection):
126
125
 
127
126
  return patches, sizes
128
127
 
128
+ def _update_before_draw(self) -> None:
129
+ """Update the arrow paths and directions before drawing, based on the edge collection."""
130
+ self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
131
+
132
+ trans = self.get_offset_transform().transform
133
+
134
+ for i, epath in enumerate(self._edge_collection.get_paths()):
135
+ # Offset the arrow to point to the end of the edge
136
+ self._offsets[i] = epath.vertices[-1]
137
+
138
+ # Rotate the arrow to point in the direction of the edge
139
+ apath = self._paths[i]
140
+ # NOTE: because the tip of the arrow is at (0, 0) in patch space,
141
+ # in theory it will rotate around that point already
142
+ v2 = trans(epath.vertices[-1])
143
+ v1 = trans(epath.vertices[-2])
144
+ dv = v2 - v1
145
+ theta = atan2(*(dv[::-1]))
146
+ theta_old = self._angles[i]
147
+ dtheta = theta - theta_old
148
+ mrot = np.array([[cos(dtheta), sin(dtheta)], [-sin(dtheta), cos(dtheta)]])
149
+ apath.vertices = apath.vertices @ mrot
150
+ self._angles[i] = theta
151
+
129
152
  def set_array(self, A: np.ndarray) -> Never:
130
153
  """Set the array for cmap/norm coloring, but keep the facecolors as set (usually 'none')."""
131
154
  raise ValueError("Setting an array for arrows directly is not supported.")
@@ -145,7 +168,7 @@ class EdgeArrowCollection(mpl.collections.PatchCollection):
145
168
 
146
169
  @mpl.artist.allow_rasterization
147
170
  def draw(self, renderer):
148
- self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
171
+ self._update_before_draw()
149
172
  super().draw(renderer)
150
173
 
151
174
 
iplotx/edge/geometry.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Support module with geometry- and path-related functions for edges.
3
3
 
4
- 3D geometry is in its separate module :mod:`.geometry3d`.
4
+ 3D geometry is in a separate module.
5
5
  """
6
6
 
7
7
  from typing import (
@@ -16,7 +16,6 @@ from ..typing import (
16
16
  Pair,
17
17
  )
18
18
  from .ports import _get_port_unit_vector
19
- from .geometry3d import _compute_edge_path_3d
20
19
 
21
20
 
22
21
  def _compute_loops_per_angle(nloops, angles):
@@ -71,10 +70,10 @@ def _get_shorter_edge_coords(vpath, vsize, theta, shrink=0):
71
70
  """Get the coordinates of an edge tip such that it touches the vertex border.
72
71
 
73
72
  Parameters:
74
- vpath: The vertex path, in figure coordinates (so scaled by dpi).
75
- vsize: The vertex max size, in figure coordinates (so scaled by dpi).
76
- theta: The angle of the edge inpinging into the vertex, in radians, in figure coordinates.
77
- shrink: Additional shrinking of the edge, in figure coordinates (so scaled by dpi).
73
+ vpath: the vertex path, in figure coordinates (so scaled by dpi).
74
+ vsize: the vertex max size, in figure coordinates (so scaled by dpi).
75
+ theta: the angle of the edge inpinging into the vertex, in radians, in figure coordinates.
76
+ shrink: additional shrinking of the edge, in figure coordinates (so scaled by dpi).
78
77
  """
79
78
  # Bound theta from -pi to pi (why is that not guaranteed?)
80
79
  theta = (theta + pi) % (2 * pi) - pi
@@ -479,7 +478,7 @@ def _compute_edge_path_curved(
479
478
  return path, tuple(thetas)
480
479
 
481
480
 
482
- def _compute_edge_path_2d(
481
+ def _compute_edge_path(
483
482
  *args,
484
483
  tension: float = 0,
485
484
  waypoints: str | tuple[float, float] | Sequence[tuple[float, float]] | np.ndarray = "none",
@@ -513,25 +512,3 @@ def _compute_edge_path_2d(
513
512
  ports=ports,
514
513
  **kwargs,
515
514
  )
516
-
517
-
518
- def _compute_edge_path(
519
- vcoord_data,
520
- *args,
521
- **kwargs,
522
- ):
523
- """Compute the edge path in either 2D or 3D.
524
-
525
- Parameters:
526
- vcoord_data: The vertex coordinates in data coordinates. This is used to
527
- determine the dimensionality of the layout.
528
- *args: Additional arguments passed to the internal functions.
529
- **kwargs: Additional keyword arguments passed to the internal functions.
530
-
531
- Returns:
532
- The computed edge path and the angles at the start and end of the edge.
533
- """
534
- ndim = len(vcoord_data[0])
535
- if ndim == 2:
536
- return _compute_edge_path_2d(vcoord_data, *args, **kwargs)
537
- return _compute_edge_path_3d(vcoord_data, *args, **kwargs)
iplotx/groups.py CHANGED
@@ -65,7 +65,7 @@ class GroupingArtist(PatchCollection):
65
65
  self._points_per_curve = points_per_curve
66
66
 
67
67
  network = kwargs.pop("network", None)
68
- self.layout = normalise_layout(layout, network=network)
68
+ self.layout = layout = normalise_layout(layout, network=network)
69
69
  self.ndim = layout.shape[1]
70
70
 
71
71
  patches, grouping, coords_hulls = self._create_patches(
iplotx/label.py CHANGED
@@ -77,6 +77,16 @@ class LabelCollection(mpl.artist.Artist):
77
77
  child.set_figure(fig)
78
78
  self._update_offsets(dpi=fig.dpi)
79
79
 
80
+ @property
81
+ def axes(self):
82
+ return mpl.artist.Artist.axes.__get__(self)
83
+
84
+ @axes.setter
85
+ def axes(self, new_axes):
86
+ mpl.artist.Artist.axes.__set__(self, new_axes)
87
+ for child in self.get_children():
88
+ child.axes = new_axes
89
+
80
90
  def set_transform(self, transform: mpl.transforms.Transform) -> None:
81
91
  """Set the transform for this artist and children.
82
92
 
iplotx/network.py CHANGED
@@ -384,10 +384,8 @@ class NetworkArtist(mpl.artist.Artist):
384
384
  # Handle zorder manually, just like in AxesBase in mpl
385
385
  children = list(self.get_children())
386
386
  children.sort(key=lambda x: x.zorder)
387
- for art in children:
388
- if (self.get_ndim() == 3) and (art.axes is not None):
389
- art.do_3d_projection()
390
- art.draw(renderer)
387
+ for child in children:
388
+ child.draw(renderer)
391
389
 
392
390
 
393
391
  def _update_from_internal(style, row, kind):
iplotx/version.py CHANGED
@@ -2,4 +2,4 @@
2
2
  iplotx version information module.
3
3
  """
4
4
 
5
- __version__ = "0.10.0"
5
+ __version__ = "0.11.1"
iplotx/vertex.py CHANGED
@@ -315,6 +315,10 @@ class VertexCollection(PatchCollection):
315
315
  transform=transform,
316
316
  )
317
317
 
318
+ def get_ndim(self):
319
+ """Get the number of dimensions of the layout."""
320
+ return self._layout.shape[1]
321
+
318
322
  def get_labels(self):
319
323
  """Get the vertex labels.
320
324
 
@@ -361,6 +365,10 @@ class VertexCollection(PatchCollection):
361
365
  rotations = np.arctan2(doffsets_fig[:, 1], doffsets_fig[:, 0])
362
366
  self.get_labels().set_rotations(rotations)
363
367
 
368
+ def _update_before_draw(self) -> None:
369
+ """Update the collection before drawing."""
370
+ self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
371
+
364
372
  @mpl.artist.allow_rasterization
365
373
  def draw(self, renderer):
366
374
  if not self.get_visible():
@@ -371,7 +379,8 @@ class VertexCollection(PatchCollection):
371
379
  if len(self.get_paths()) == 0:
372
380
  return
373
381
 
374
- self.set_sizes(self._sizes, self.get_figure(root=True).dpi)
382
+ self._update_before_draw()
383
+ super().draw(renderer)
375
384
 
376
385
  # Set the label rotations already, hopefully this is not too early
377
386
  self._update_children()
@@ -379,7 +388,6 @@ class VertexCollection(PatchCollection):
379
388
  # NOTE: This draws the vertices first, then the labels.
380
389
  # The correct order would be vertex1->label1->vertex2->label2, etc.
381
390
  # We might fix if we manage to find a way to do it.
382
- super().draw(renderer)
383
391
  for child in self.get_children():
384
392
  child.draw(renderer)
385
393
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iplotx
3
- Version: 0.10.0
3
+ Version: 0.11.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
@@ -1,21 +1,22 @@
1
1
  iplotx/__init__.py,sha256=RzSct91jO8abrxOIn33rKEnDUgYpu1oj4olbObgX_hs,489
2
2
  iplotx/artists.py,sha256=XNtRwuvQdKkZCAejILydLD3J5B87sg5xPXuZFv_Gkk8,654
3
3
  iplotx/cascades.py,sha256=OPqF7Huls-HFmDA5MCF6DEZlUeRVaXsbQcHBoKAgNJs,8182
4
- iplotx/groups.py,sha256=X0G-EULkd7WBn1j82r-cBgpzZRd7gQ1cfqFoYNweLns,6775
5
- iplotx/label.py,sha256=76vSaH9zPv8drpzFaWNwuEIMQPEgKmilwfBnA8I_BJ4,9307
4
+ iplotx/groups.py,sha256=g6ahm61BSBmd2weIjr40MvPi_GcNRgvNb9YklQsiza4,6784
5
+ iplotx/label.py,sha256=7eS8ByadrhdIFOZz19U4VrS-oXY_ndFYNB-D4RZbFqI,9573
6
6
  iplotx/layout.py,sha256=KxmRLqjo8AYCBAmXez8rIiLU2sM34qhb6ox9AHYwRyE,4839
7
- iplotx/network.py,sha256=_yEArzsqnzm5MefVKKM96q_Od47ZIwjkJb2wuVFnfD8,13486
7
+ iplotx/network.py,sha256=ae5rZwzWxmcBQXx1Y0q24jaXcM1hT1kip-JKsyk11QY,13385
8
8
  iplotx/plotting.py,sha256=icEefWJnS2lEGLp4t1LhDSP40JuvNKgOie3FDLOnTMk,13195
9
9
  iplotx/tree.py,sha256=TxbNoBHS0CfswrcMIWCNtnOl_3e4-PwCrVo0goywC0U,28807
10
10
  iplotx/typing.py,sha256=QLdzV358IiD1CFe88MVp0D77FSx5sSAVUmM_2WPPE8I,1463
11
- iplotx/version.py,sha256=YEqH52lnz6XyxsG7HXlFaHzcWyc3-VsDrQeE7vZtRQQ,67
12
- iplotx/vertex.py,sha256=rIg1gdxv7ZW8HjqmwJaynm098HqTmlC9XQgB81wjzgk,15775
13
- iplotx/art3d/edge.py,sha256=cZzI0nPTglU1xA_TsySrdE5GwxVx7smc32mxCE_EP48,1785
14
- iplotx/art3d/vertex.py,sha256=KhwR60ekPLL1CDYn9jeQFo5kfdCS5Naz7u1kM-_eq7U,1883
15
- iplotx/edge/__init__.py,sha256=P96eXHECrqHtVeIxdBA0SxJvTMeHlXPP_bqEOi5MsVQ,27589
16
- iplotx/edge/arrow.py,sha256=ZKt3UNZ7XRa2S3KxpoQfd4q_6eSUHOS476BZNqlf2pw,16462
17
- iplotx/edge/geometry.py,sha256=G0hze1SQGUiiLMdc8QVO7zr1C9UUIQt-IN17KBk6lkM,16317
18
- iplotx/edge/geometry3d.py,sha256=HnL1TvMXFegvco6oaiUqDXRKbx9GW3FsT4DUX_Ol94k,3207
11
+ iplotx/version.py,sha256=BVfqBj50ae0jogJGXsQXi_fML0WjuC4GLFRHZQWawYY,67
12
+ iplotx/vertex.py,sha256=bjvAy9UciPWkA1J-SroWF9ZaTXRzNKtDZXBlZ80VM60,16026
13
+ iplotx/art3d/vertex.py,sha256=Xf8Um30X2doCd8KdNN7332F6BxC4k72Mb_GeRAuzQfQ,2545
14
+ iplotx/art3d/edge/__init__.py,sha256=EzzW06YEeyIu52gXormkGIobae-etwKevZ_PDBr-S9c,4624
15
+ iplotx/art3d/edge/arrow.py,sha256=14BFXY9kDOUGPZl2fMD9gRVGyaaN5kyd-l6ikBg6WHU,3601
16
+ iplotx/art3d/edge/geometry.py,sha256=76VUmpPG-4Mls7x_994dMwdDPrWWnjT7nHJsHfwK_hA,2467
17
+ iplotx/edge/__init__.py,sha256=wMKXD1h5SBaUv6HmebIc5wc9k8AuukaXzAOBu7epaqA,26341
18
+ iplotx/edge/arrow.py,sha256=U7vvBo7IMwo1qiyU9cyUEwraOaBcJLgdu9oU2OyoHL4,17453
19
+ iplotx/edge/geometry.py,sha256=jkTMvQC5425GjB_fmGLIPJeSDAr_7NZF8zZDLTrSj34,15541
19
20
  iplotx/edge/leaf.py,sha256=SyGMv2PIOoH0pey8-aMVaZheK3hNe1Qz_okcyWbc4E4,4268
20
21
  iplotx/edge/ports.py,sha256=BpkbiEhX4mPBBAhOv4jcKFG4Y8hxXz5GRtVLCC0jbtI,1235
21
22
  iplotx/ingest/__init__.py,sha256=S0YfnXcFKseB7ZBQc4yRt0cNDsLlhqdom0TmSY3OY2E,4756
@@ -36,6 +37,6 @@ iplotx/utils/geometry.py,sha256=6RrC6qaB0-1vIk1LhGA4CfsiMd-9JNniSPyL_l9mshE,9245
36
37
  iplotx/utils/internal.py,sha256=WWfcZDGK8Ut1y_tOHRGg9wSqY1bwSeLQO7dHM_8Tvwo,107
37
38
  iplotx/utils/matplotlib.py,sha256=wELE73quQv10-1w9uA5eDTgkZkylJvjg7pd3K5tZPOo,6294
38
39
  iplotx/utils/style.py,sha256=vyNP80nDYVinqm6_9ltCJCtjK35ZcGlHvOskNv3eQBc,4225
39
- iplotx-0.10.0.dist-info/METADATA,sha256=BY0tMOjP84ydubHd9ocmpn9LaqdmoqX3VcBEX_6tTWA,4880
40
- iplotx-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
- iplotx-0.10.0.dist-info/RECORD,,
40
+ iplotx-0.11.1.dist-info/METADATA,sha256=1F0wH64PAw1_Bx-Qv7RSTgz6nwoQfcbRDJjmlR3KYfc,4880
41
+ iplotx-0.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
42
+ iplotx-0.11.1.dist-info/RECORD,,
iplotx/art3d/edge.py DELETED
@@ -1,65 +0,0 @@
1
- """
2
- Module containing code to manipulate edge visualisations in 3D, especially the Edge3DCollection class.
3
- """
4
-
5
- from mpl_toolkits.mplot3d.art3d import (
6
- Line3DCollection,
7
- )
8
-
9
- from ..utils.matplotlib import (
10
- _forwarder,
11
- )
12
- from ..edge import (
13
- EdgeCollection,
14
- )
15
-
16
-
17
- @_forwarder(
18
- (
19
- "set_clip_path",
20
- "set_clip_box",
21
- "set_snap",
22
- "set_sketch_params",
23
- "set_animated",
24
- "set_picker",
25
- )
26
- )
27
- class Edge3DCollection(Line3DCollection):
28
- """Collection of vertex patches for plotting."""
29
-
30
- pass
31
-
32
-
33
- def edge_collection_2d_to_3d(
34
- col: EdgeCollection,
35
- zdir: str = "z",
36
- depthshade: bool = True,
37
- axlim_clip: bool = False,
38
- ):
39
- """Convert a 2D EdgeCollection to a 3D Edge3DCollection.
40
-
41
- Parameters:
42
- col: The 2D EdgeCollection to convert.
43
- zs: The z coordinate(s) to use for the 3D vertices.
44
- zdir: The axis to use as the z axis (default is "z").
45
- depthshade: Whether to apply depth shading (default is True).
46
- axlim_clip: Whether to clip the vertices to the axes limits (default is False).
47
- """
48
- if not isinstance(col, EdgeCollection):
49
- raise TypeError("vertices must be a VertexCollection")
50
-
51
- # TODO: if we make Edge3DCollection a dynamic drawer, this will need to change
52
- # fundamentally. Also, this currently does not handle labels properly.
53
- vinfo = col._get_adjacent_vertices_info()
54
-
55
- segments3d = []
56
- for offset1, offset2 in vinfo["offsets"]:
57
- segment = [tuple(offset1), tuple(offset2)]
58
- segments3d.append(segment)
59
-
60
- # NOTE: after this line, none of the EdgeCollection methods will work
61
- # It's become a static drawer now
62
- col.__class__ = Edge3DCollection
63
-
64
- col.set_segments(segments3d)
65
- col._axlim_clip = axlim_clip