iplotx 0.3.1__py3-none-any.whl → 0.5.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/__init__.py CHANGED
@@ -11,6 +11,9 @@ from .plotting import (
11
11
  network,
12
12
  tree,
13
13
  )
14
+ import iplotx.artists as artists
15
+ import iplotx.style as style
16
+
14
17
 
15
18
  # Shortcut to iplotx.plotting.network
16
19
  plot = network
@@ -19,5 +22,7 @@ __all__ = [
19
22
  "network",
20
23
  "tree",
21
24
  "plot",
25
+ "artists",
26
+ "style",
22
27
  "__version__",
23
28
  ]
iplotx/artists.py ADDED
@@ -0,0 +1,24 @@
1
+ """
2
+ All artists defined in iplotx.
3
+ """
4
+
5
+ from .network import NetworkArtist
6
+ from .tree import TreeArtist
7
+ from .vertex import VertexCollection
8
+ from .edge import EdgeCollection
9
+ from .label import LabelCollection
10
+ from .edge.arrow import EdgeArrowCollection
11
+ from .edge.leaf import LeafEdgeCollection
12
+ from .cascades import CascadeCollection
13
+
14
+
15
+ ___all__ = (
16
+ NetworkArtist,
17
+ TreeArtist,
18
+ VertexCollection,
19
+ EdgeCollection,
20
+ LeafEdgeCollection,
21
+ LabelCollection,
22
+ EdgeArrowCollection,
23
+ CascadeCollection,
24
+ )
iplotx/cascades.py CHANGED
@@ -2,6 +2,7 @@ from typing import (
2
2
  Any,
3
3
  Optional,
4
4
  )
5
+ import warnings
5
6
  import numpy as np
6
7
  import pandas as pd
7
8
 
@@ -38,7 +39,7 @@ class CascadeCollection(mpl.collections.PatchCollection):
38
39
 
39
40
  # NOTE: there is a weird bug in pandas when using generic Hashable-s
40
41
  # with .loc. Seems like doing .T[...] works for individual index
41
- # elements only though
42
+ # elements only though (i.e. using __getitem__ a la dict)
42
43
  def get_node_coords(node):
43
44
  return layout.T[node].values
44
45
 
@@ -54,9 +55,19 @@ class CascadeCollection(mpl.collections.PatchCollection):
54
55
  # These patches need at least a facecolor (usually) or an edgecolor
55
56
  # so it's safe to make a list from these
56
57
  nodes_unordered = set()
57
- for prop in ("facecolor", "edgecolor"):
58
+ for prop in ("facecolor", "edgecolor", "linewidth", "linestyle"):
58
59
  if prop in style:
59
- nodes_unordered |= set(style[prop].keys())
60
+ value = style[prop]
61
+ if isinstance(value, dict):
62
+ nodes_unordered |= set(value.keys())
63
+
64
+ if len(nodes_unordered) == 0:
65
+ warnings.warn(
66
+ "No nodes found in the style for the cascading patches. "
67
+ "Please provide a style with at least one dict-like "
68
+ "specification among the following properties: 'facecolor', "
69
+ "'edgecolor', 'color', 'linewidth', or 'linestyle'.",
70
+ )
60
71
 
61
72
  # Draw the patches from the closest to the root (earlier drawing)
62
73
  # to the closer to the leaves (later drawing).
@@ -70,32 +81,15 @@ class CascadeCollection(mpl.collections.PatchCollection):
70
81
  f"Cascading patches not implemented for layout: {layout_name}.",
71
82
  )
72
83
 
73
- nleaves = sum(1 for leaf in provider(tree).get_leaves())
74
- extend_mode = style.get("extend", False)
75
- if extend_mode and (extend_mode != "leaf_labels"):
76
- if layout_name == "horizontal":
77
- if orientation == "right":
78
- maxdepth = layout.values[:, 0].max()
79
- else:
80
- maxdepth = layout.values[:, 0].min()
81
- elif layout_name == "vertical":
82
- if orientation == "descending":
83
- maxdepth = layout.values[:, 1].min()
84
- else:
85
- maxdepth = layout.values[:, 1].max()
86
- elif layout_name == "radial":
87
- # layout values are: r, theta
88
- maxdepth = layout.values[:, 0].max()
89
84
  self._maxdepth = maxdepth
90
85
 
91
86
  cascading_patches = []
87
+ nleaves = sum(1 for leaf in provider(tree).get_leaves())
92
88
  for node in drawing_order:
93
89
  stylei = rotate_style(style, key=node)
94
90
  stylei.pop("extend", None)
95
91
  # Default alpha is 0.5 for simple colors
96
- if isinstance(stylei.get("facecolor", None), str) and (
97
- "alpha" not in stylei
98
- ):
92
+ if isinstance(stylei.get("facecolor", None), str) and ("alpha" not in stylei):
99
93
  stylei["alpha"] = 0.5
100
94
 
101
95
  provider_node = provider(node)
@@ -137,9 +131,7 @@ class CascadeCollection(mpl.collections.PatchCollection):
137
131
  rmax = maxdepth if extend else leaves_coords[:, 0].max()
138
132
  thetamin = leaves_coords[:, 1].min() - 0.5 * dtheta
139
133
  thetamax = leaves_coords[:, 1].max() + 0.5 * dtheta
140
- thetas = np.linspace(
141
- thetamin, thetamax, max(30, (thetamax - thetamin) // 3)
142
- )
134
+ thetas = np.linspace(thetamin, thetamax, max(30, (thetamax - thetamin) // 3))
143
135
  xs = list(rmin * np.cos(thetas)) + list(rmax * np.cos(thetas[::-1]))
144
136
  ys = list(rmin * np.sin(thetas)) + list(rmax * np.sin(thetas[::-1]))
145
137
  points = list(zip(xs, ys))
@@ -200,23 +192,22 @@ class CascadeCollection(mpl.collections.PatchCollection):
200
192
  for path in self.get_paths():
201
193
  # Old radii
202
194
  r2old = np.linalg.norm(path.vertices[-2])
203
- path.vertices[(len(path.vertices) - 1) // 2 :] *= (
204
- self.get_maxdepth() / r2old
205
- )
195
+ # Update the outer part of the wedge patch
196
+ path.vertices[(len(path.vertices) - 1) // 2 :] *= self.get_maxdepth() / r2old
206
197
  return
207
198
 
208
199
  if (layout_name, orientation) == ("horizontal", "right"):
209
200
  for path in self.get_paths():
210
201
  path.vertices[[1, 2], 0] = self.get_maxdepth()
211
- elif (layout_name, orientation) == ("horizontal", "right"):
202
+ elif (layout_name, orientation) == ("horizontal", "left"):
212
203
  for path in self.get_paths():
213
204
  path.vertices[[0, 3], 0] = self.get_maxdepth()
214
205
  elif (layout_name, orientation) == ("vertical", "descending"):
215
206
  for path in self.get_paths():
216
- path.vertices[[1, 2], 1] = self.get_maxdepth()
207
+ path.vertices[[0, 1], 1] = self.get_maxdepth()
217
208
  elif (layout_name, orientation) == ("vertical", "ascending"):
218
209
  for path in self.get_paths():
219
- path.vertices[[0, 3], 1] = self.get_maxdepth()
210
+ path.vertices[[2, 3], 1] = self.get_maxdepth()
220
211
  else:
221
212
  raise ValueError(
222
213
  f"Layout name and orientation not supported: {layout_name}, {orientation}."
iplotx/edge/__init__.py CHANGED
@@ -121,8 +121,47 @@ class EdgeCollection(mpl.collections.PatchCollection):
121
121
  transform=transform,
122
122
  )
123
123
 
124
+ if "split" in self._style:
125
+ self._add_subedges(
126
+ len(patches),
127
+ self._style["split"],
128
+ )
129
+
130
+ def _add_subedges(
131
+ self,
132
+ nedges,
133
+ style,
134
+ ):
135
+ """Add subedges to shadow the current edges."""
136
+ segments = [np.zeros((2, 2)) for i in range(nedges)]
137
+ kwargs = {
138
+ "linewidths": [],
139
+ "edgecolors": [],
140
+ "linestyles": [],
141
+ }
142
+ for i in range(nedges):
143
+ vids = self._vertex_ids[i]
144
+ stylei = rotate_style(style, index=i, key=vids, key2=vids[-1])
145
+ for key, values in kwargs.items():
146
+ # iplotx uses singular style properties
147
+ key = key.rstrip("s")
148
+ # "color" has higher priority than "edgecolor"
149
+ if (key == "edgecolor") and ("color" in stylei):
150
+ val = stylei["color"]
151
+ else:
152
+ val = stylei.get(key.rstrip("s"), getattr(self, f"get_{key}")()[i])
153
+ values.append(val)
154
+
155
+ self._subedges = mpl.collections.LineCollection(
156
+ segments,
157
+ transform=self.get_transform(),
158
+ **kwargs,
159
+ )
160
+
124
161
  def get_children(self) -> tuple:
125
162
  children = []
163
+ if hasattr(self, "_subedges"):
164
+ children.append(self._subedges)
126
165
  if hasattr(self, "_arrows"):
127
166
  children.append(self._arrows)
128
167
  if hasattr(self, "_label_collection"):
@@ -209,7 +248,7 @@ class EdgeCollection(mpl.collections.PatchCollection):
209
248
  index = pd.Series(
210
249
  np.arange(len(index)),
211
250
  index=index,
212
- )
251
+ ).to_dict()
213
252
 
214
253
  voffsets = []
215
254
  vpaths = []
@@ -262,11 +301,13 @@ class EdgeCollection(mpl.collections.PatchCollection):
262
301
  trans_inv = transform.inverted().transform
263
302
 
264
303
  # 1. Make a list of vertices with loops, and store them for later
265
- loop_vertex_dict = defaultdict(lambda: dict(indices=[], edge_angles=[]))
266
- for i, (v1, v2) in enumerate(vids):
267
- # Postpone loops (step 3)
268
- if v1 == v2:
269
- loop_vertex_dict[v1]["indices"].append(i)
304
+ # NOTE: vinfo["loops"] can be False when we want no loops (e.g. leaf edges)
305
+ if vinfo.get("loops", True):
306
+ loop_vertex_dict = defaultdict(lambda: dict(indices=[], edge_angles=[]))
307
+ for i, (v1, v2) in enumerate(vids):
308
+ # Postpone loops (step 3)
309
+ if v1 == v2:
310
+ loop_vertex_dict[v1]["indices"].append(i)
270
311
 
271
312
  # 2. Make paths for non-loop edges
272
313
  # NOTE: keep track of parallel edges to offset them
@@ -274,7 +315,7 @@ class EdgeCollection(mpl.collections.PatchCollection):
274
315
  paths = []
275
316
  for i, (v1, v2) in enumerate(vids):
276
317
  # Postpone loops (step 3)
277
- if v1 == v2:
318
+ if vinfo.get("loops", True) and (v1 == v2):
278
319
  paths.append(None)
279
320
  continue
280
321
 
@@ -296,10 +337,20 @@ class EdgeCollection(mpl.collections.PatchCollection):
296
337
  tension = 0
297
338
  ports = None
298
339
 
340
+ # False is a synonym for "none"
299
341
  waypoints = edge_stylei.get("waypoints", "none")
342
+ if waypoints is False or waypoints is np.False_:
343
+ waypoints = "none"
344
+ elif waypoints is True or waypoints is np.True_:
345
+ raise ValueError(
346
+ "Could not determine automatically type of edge waypoints.",
347
+ )
300
348
  if waypoints != "none":
301
349
  ports = edge_stylei.get("ports", (None, None))
302
350
 
351
+ if not isinstance(waypoints, str):
352
+ __import__("ipdb").set_trace()
353
+
303
354
  # Compute actual edge path
304
355
  path, angles = _compute_edge_path(
305
356
  vcoord_data,
@@ -329,11 +380,23 @@ class EdgeCollection(mpl.collections.PatchCollection):
329
380
  if (offset != 0).any():
330
381
  path.vertices[:] = trans_inv(trans(path.vertices) + offset)
331
382
 
383
+ # If splitting is active, split the path here, shedding off the last straight
384
+ # segment but only if waypoints were used
385
+ if hasattr(self, "_subedges") and waypoints != "none":
386
+ # NOTE: we are already in the middle of a redraw, so we can happily avoid
387
+ # causing stale of the subedges. They are already scheduled to be redrawn
388
+ # at the end of this function.
389
+ self._subedges._paths[i].vertices[:] = path.vertices[-2:].copy()
390
+ # NOTE: instead of shortening the path, we just make the last bit invisible
391
+ # that makes it easier on memory management etc.
392
+ path.vertices[-1] = path.vertices[-2]
393
+
332
394
  # Collect angles for this vertex, to be used for loops plotting below
333
- if v1 in loop_vertex_dict:
334
- loop_vertex_dict[v1]["edge_angles"].append(angles[0])
335
- if v2 in loop_vertex_dict:
336
- loop_vertex_dict[v2]["edge_angles"].append(angles[1])
395
+ if vinfo.get("loops", True):
396
+ if v1 in loop_vertex_dict:
397
+ loop_vertex_dict[v1]["edge_angles"].append(angles[0])
398
+ if v2 in loop_vertex_dict:
399
+ loop_vertex_dict[v2]["edge_angles"].append(angles[1])
337
400
 
338
401
  # Add the path for this non-loop edge
339
402
  paths.append(path)
@@ -360,41 +423,45 @@ class EdgeCollection(mpl.collections.PatchCollection):
360
423
  )
361
424
 
362
425
  # 3. Deal with loops at the end
363
- for vid, ldict in loop_vertex_dict.items():
364
- vpath = vpaths[ldict["indices"][0]][0]
365
- vsize = vsizes[ldict["indices"][0]][0]
366
- vcoord_fig = trans(vcenters[ldict["indices"][0]][0])
367
- nloops = len(ldict["indices"])
368
- edge_angles = ldict["edge_angles"]
369
-
370
- # The space between the existing angles is where we can fit the loops
371
- # One loop we can fit in the largest wedge, multiple loops we need
372
- nloops_per_angle = _compute_loops_per_angle(nloops, edge_angles)
373
-
374
- idx = 0
375
- for theta1, theta2, nloops in nloops_per_angle:
376
- # Angular size of each loop in this wedge
377
- delta = (theta2 - theta1) / nloops
378
-
379
- # Iterate over individual loops
380
- for j in range(nloops):
381
- thetaj1 = theta1 + j * delta + max(delta - loopmaxangle, 0) / 2
382
- thetaj2 = thetaj1 + min(delta, loopmaxangle)
383
-
384
- # Get the path for this loop
385
- path = _compute_loop_path(
386
- vcoord_fig,
387
- vpath,
388
- vsize,
389
- thetaj1,
390
- thetaj2,
391
- trans_inv,
392
- looptension=self._style.get("looptension", 2.5),
393
- )
394
- paths[ldict["indices"][idx]] = path
395
- idx += 1
426
+ if vinfo.get("loops", True):
427
+ for vid, ldict in loop_vertex_dict.items():
428
+ vpath = vpaths[ldict["indices"][0]][0]
429
+ vsize = vsizes[ldict["indices"][0]][0]
430
+ vcoord_fig = trans(vcenters[ldict["indices"][0]][0])
431
+ nloops = len(ldict["indices"])
432
+ edge_angles = ldict["edge_angles"]
433
+
434
+ # The space between the existing angles is where we can fit the loops
435
+ # One loop we can fit in the largest wedge, multiple loops we need
436
+ nloops_per_angle = _compute_loops_per_angle(nloops, edge_angles)
437
+
438
+ idx = 0
439
+ for theta1, theta2, nloops in nloops_per_angle:
440
+ # Angular size of each loop in this wedge
441
+ delta = (theta2 - theta1) / nloops
442
+
443
+ # Iterate over individual loops
444
+ for j in range(nloops):
445
+ thetaj1 = theta1 + j * delta + max(delta - loopmaxangle, 0) / 2
446
+ thetaj2 = thetaj1 + min(delta, loopmaxangle)
447
+
448
+ # Get the path for this loop
449
+ path = _compute_loop_path(
450
+ vcoord_fig,
451
+ vpath,
452
+ vsize,
453
+ thetaj1,
454
+ thetaj2,
455
+ trans_inv,
456
+ looptension=self._style.get("looptension", 2.5),
457
+ )
458
+ paths[ldict["indices"][idx]] = path
459
+ idx += 1
396
460
 
397
461
  self._paths = paths
462
+ # FIXME:??
463
+ # if hasattr(self, "_subedges"):
464
+ # self._subedges.stale = True
398
465
 
399
466
  def _update_labels(self):
400
467
  if self._labels is None:
@@ -459,13 +526,13 @@ class EdgeCollection(mpl.collections.PatchCollection):
459
526
  if not self.get_visible():
460
527
  return
461
528
 
529
+ # This includes the subedges if present
462
530
  self._update_paths()
463
531
  # This sets the arrow offsets
464
532
  self._update_children()
465
533
 
466
534
  super().draw(renderer)
467
535
  for child in self.get_children():
468
- # This sets the arrow sizes with dpi scaling
469
536
  child.draw(renderer)
470
537
 
471
538
  def get_ports(self) -> Optional[LeafProperty[Pair[Optional[str]]]]:
@@ -485,7 +552,8 @@ class EdgeCollection(mpl.collections.PatchCollection):
485
552
  edge end.
486
553
  """
487
554
  if ports is None:
488
- del self._style["ports"]
555
+ if "ports" in self._style:
556
+ del self._style["ports"]
489
557
  else:
490
558
  self._style["ports"] = ports
491
559
  self.stale = True
@@ -519,7 +587,8 @@ class EdgeCollection(mpl.collections.PatchCollection):
519
587
 
520
588
  """
521
589
  if tension is None:
522
- del self._style["tension"]
590
+ if "tension" in self._style:
591
+ del self._style["tension"]
523
592
  else:
524
593
  self._style["tension"] = tension
525
594
  self.stale = True
@@ -579,7 +648,8 @@ class EdgeCollection(mpl.collections.PatchCollection):
579
648
  looptension: The tension to use for loops. If None, the default is 2.5.
580
649
  """
581
650
  if looptension is None:
582
- del self._style["looptension"]
651
+ if "looptension" in self._style:
652
+ del self._style["looptension"]
583
653
  else:
584
654
  self._style["looptension"] = looptension
585
655
  self.stale = True
@@ -599,7 +669,8 @@ class EdgeCollection(mpl.collections.PatchCollection):
599
669
  offset: The offset in points for parallel straight edges. If None, the default is 3.
600
670
  """
601
671
  if offset is None:
602
- del self._style["offset"]
672
+ if "offset" in self._style:
673
+ del self._style["offset"]
603
674
  else:
604
675
  self._style["offset"] = offset
605
676
  self.stale = True
@@ -629,6 +700,8 @@ def make_stub_patch(**kwargs):
629
700
  "offset",
630
701
  "paralleloffset",
631
702
  "cmap",
703
+ "norm",
704
+ "split",
632
705
  ]
633
706
  for prop in forbidden_props:
634
707
  if prop in kwargs:
iplotx/edge/arrow.py CHANGED
@@ -2,6 +2,8 @@
2
2
  Module for edge arrows in iplotx.
3
3
  """
4
4
 
5
+ from typing import Never
6
+
5
7
  import numpy as np
6
8
  import matplotlib as mpl
7
9
  from matplotlib.patches import PathPatch
@@ -124,12 +126,17 @@ class EdgeArrowCollection(mpl.collections.PatchCollection):
124
126
 
125
127
  return patches, sizes
126
128
 
127
- def set_array(self, A):
129
+ def set_array(self, A: np.ndarray) -> Never:
128
130
  """Set the array for cmap/norm coloring, but keep the facecolors as set (usually 'none')."""
129
131
  raise ValueError("Setting an array for arrows directly is not supported.")
130
132
 
131
- def set_colors(self, colors):
132
- """Set arrow colors (edge and/or face) based on a colormap."""
133
+ def set_colors(self, colors: np.ndarray) -> None:
134
+ """Set arrow colors (edge and/or face) based on a colormap.
135
+
136
+ Parameters:
137
+ colors: Color array to apply. This must be an Nx3 or Nx4 vector of RGB or RGBA colors.
138
+ This function will NOT attempt to convert other color descriptions to RGB/RGBA.
139
+ """
133
140
  # NOTE: facecolors is always an array because we come from patches
134
141
  # It can have zero alpha (i.e. if we choose "none", or a hollow marker)
135
142
  self.set_edgecolor(colors)
@@ -145,6 +152,8 @@ class EdgeArrowCollection(mpl.collections.PatchCollection):
145
152
  def make_arrow_patch(marker: str = "|>", width: float = 8, **kwargs):
146
153
  """Make a patch of the given marker shape and size."""
147
154
  height = kwargs.pop("height", width * 1.3)
155
+ if height == "width":
156
+ height = width
148
157
 
149
158
  # Normalise by the max size, this is taken care of in _transforms
150
159
  # subsequently in a way that is nice to dpi scaling
@@ -169,6 +178,38 @@ def make_arrow_patch(marker: str = "|>", width: float = 8, **kwargs):
169
178
  codes=[getattr(mpl.path.Path, x) for x in codes],
170
179
  closed=True,
171
180
  )
181
+ elif marker == "|\\":
182
+ codes = ["MOVETO", "LINETO", "LINETO", "CLOSEPOLY"]
183
+ if "color" in kwargs:
184
+ kwargs["facecolor"] = kwargs["edgecolor"] = kwargs.pop("color")
185
+ path = mpl.path.Path(
186
+ np.array(
187
+ [
188
+ [-height, width * 0.5],
189
+ [-height, 0],
190
+ [0, 0],
191
+ [-height, width * 0.5],
192
+ ]
193
+ ),
194
+ codes=[getattr(mpl.path.Path, x) for x in codes],
195
+ closed=True,
196
+ )
197
+ elif marker == "|/":
198
+ codes = ["MOVETO", "LINETO", "LINETO", "CLOSEPOLY"]
199
+ if "color" in kwargs:
200
+ kwargs["facecolor"] = kwargs["edgecolor"] = kwargs.pop("color")
201
+ path = mpl.path.Path(
202
+ np.array(
203
+ [
204
+ [-height, 0],
205
+ [-height, -width * 0.5],
206
+ [0, 0],
207
+ [-height, 0],
208
+ ]
209
+ ),
210
+ codes=[getattr(mpl.path.Path, x) for x in codes],
211
+ closed=True,
212
+ )
172
213
  elif marker == ">":
173
214
  kwargs["facecolor"] = "none"
174
215
  if "color" in kwargs:
iplotx/edge/geometry.py CHANGED
@@ -96,6 +96,7 @@ def _get_shorter_edge_coords(vpath, vsize, theta):
96
96
  xe = v1[0]
97
97
  else:
98
98
  m12 = (v2[1] - v1[1]) / (v2[0] - v1[0])
99
+ print(m12, mtheta)
99
100
  xe = (v1[1] - m12 * v1[0]) / (mtheta - m12)
100
101
  ye = mtheta * xe
101
102
  ve = np.array([xe, ye])
@@ -171,25 +172,41 @@ def _compute_edge_path_straight(
171
172
  vsize_fig,
172
173
  trans,
173
174
  trans_inv,
175
+ layout_coordinate_system: str = "cartesian",
174
176
  **kwargs,
175
177
  ):
178
+ if layout_coordinate_system not in ("cartesian", "polar"):
179
+ raise ValueError(
180
+ f"Layout coordinate system not supported for straight edges: {layout_coordinate_system}.",
181
+ )
182
+
183
+ if layout_coordinate_system == "polar":
184
+ r0, theta0 = vcoord_data[0]
185
+ r1, theta1 = vcoord_data[1]
186
+ vcoord_data_cart = np.array(
187
+ [
188
+ [r0 * np.cos(theta0), r0 * np.sin(theta0)],
189
+ [r1 * np.cos(theta1), r1 * np.sin(theta1)],
190
+ ]
191
+ )
192
+ else:
193
+ vcoord_data_cart = vcoord_data
176
194
 
177
195
  # Coordinates in figure (default) coords
178
- vcoord_fig = trans(vcoord_data)
196
+ vcoord_fig = trans(vcoord_data_cart)
179
197
 
180
198
  points = []
181
199
 
182
200
  # Angle of the straight line
183
201
  theta = atan2(*((vcoord_fig[1] - vcoord_fig[0])[::-1]))
202
+ print(vcoord_data_cart, vcoord_fig, theta)
184
203
 
185
204
  # Shorten at starting vertex
186
205
  vs = _get_shorter_edge_coords(vpath_fig[0], vsize_fig[0], theta) + vcoord_fig[0]
187
206
  points.append(vs)
188
207
 
189
208
  # Shorten at end vertex
190
- ve = (
191
- _get_shorter_edge_coords(vpath_fig[1], vsize_fig[1], theta + pi) + vcoord_fig[1]
192
- )
209
+ ve = _get_shorter_edge_coords(vpath_fig[1], vsize_fig[1], theta + pi) + vcoord_fig[1]
193
210
  points.append(ve)
194
211
 
195
212
  codes = ["MOVETO", "LINETO"]
@@ -213,7 +230,6 @@ def _compute_edge_path_waypoints(
213
230
  ports: Pair[Optional[str]] = (None, None),
214
231
  **kwargs,
215
232
  ):
216
-
217
233
  if waypoints in ("x0y1", "y0x1"):
218
234
  assert layout_coordinate_system == "cartesian"
219
235
 
@@ -236,8 +252,7 @@ def _compute_edge_path_waypoints(
236
252
 
237
253
  # Shorten at vertex border
238
254
  vshorts[i] = (
239
- _get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i])
240
- + vcoord_fig[i]
255
+ _get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i]) + vcoord_fig[i]
241
256
  )
242
257
 
243
258
  # Shorten waypoints to keep the angles right
@@ -285,10 +300,7 @@ def _compute_edge_path_waypoints(
285
300
  theta = atan2(*(_get_port_unit_vector(ports[i], trans_inv)[::-1]))
286
301
 
287
302
  # Shorten at vertex border
288
- vshort = (
289
- _get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], theta)
290
- + vcoord_fig[i]
291
- )
303
+ vshort = _get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], theta) + vcoord_fig[i]
292
304
  thetas.append(theta)
293
305
  vshorts.append(vshort)
294
306
 
@@ -305,16 +317,12 @@ def _compute_edge_path_waypoints(
305
317
  idx_outer = 1 - idx_inner
306
318
  alpha_outer = [alpha0, alpha1][idx_outer]
307
319
 
308
- # FIXME: this is aware of chirality as stored by the layout function
309
320
  betas = np.linspace(alpha0, alpha1, points_per_curve)
310
321
  waypoints = [r0, r1][idx_inner] * np.vstack([np.cos(betas), np.sin(betas)]).T
311
- endpoint = [r0, r1][idx_outer] * np.array(
312
- [np.cos(alpha_outer), np.sin(alpha_outer)]
313
- )
322
+ endpoint = [r0, r1][idx_outer] * np.array([np.cos(alpha_outer), np.sin(alpha_outer)])
314
323
  points = np.array(list(waypoints) + [endpoint])
315
324
  points = trans(points)
316
325
  codes = ["MOVETO"] + ["LINETO"] * len(waypoints)
317
- # FIXME: same as previus comment
318
326
  angles = (alpha0 + pi / 2, alpha1)
319
327
 
320
328
  else:
@@ -391,10 +399,7 @@ def _compute_edge_path_curved(
391
399
  thetas = [None, None]
392
400
  for i in range(2):
393
401
  thetas[i] = atan2(*((auxs[i] - vcoord_fig[i])[::-1]))
394
- vs[i] = (
395
- _get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i])
396
- + vcoord_fig[i]
397
- )
402
+ vs[i] = _get_shorter_edge_coords(vpath_fig[i], vsize_fig[i], thetas[i]) + vcoord_fig[i]
398
403
 
399
404
  path = {
400
405
  "vertices": [
@@ -438,7 +443,11 @@ def _compute_edge_path(
438
443
  )
439
444
 
440
445
  if tension == 0:
441
- return _compute_edge_path_straight(*args, **kwargs)
446
+ return _compute_edge_path_straight(
447
+ *args,
448
+ layout_coordinate_system=layout_coordinate_system,
449
+ **kwargs,
450
+ )
442
451
 
443
452
  return _compute_edge_path_curved(
444
453
  tension,