Description
Bug summary
I am trying to create a polar plot of some data, with an annotation arrow pointing towards some other point in the plot. However, I get a StopIteration, presumably because of setting the rticks, which unexpectedly excludes the origin from the radius limits.
Code for reproduction
import numpy as np
import matplotlib.pyplot as plt
# Radius, random numbers in [0, 1)
rng = np.random.default_rng(seed=42)
r = rng.random((24,))
# 0 to 2pi
theta = np.linspace(0, 2, 24, endpoint=False) * np.pi
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
# Data to plot
ax.plot(theta, r)
ax.scatter(theta, r, c=r, cmap='viridis')
ax.set_rticks([1, 2, 3]) # Less radial ticks
ax.grid(True)
# Where the annotation arrow should point
an_r = 0.670
an_theta = 0.820
# Visualise arrow end-point
ax.scatter(an_theta, an_r)
# Create arrow
ax.annotate('',
xy=(an_theta, an_r), # theta, radius
xytext=(0, 0),
arrowprops=dict(facecolor='black', shrink=0),
horizontalalignment='right'
)
plt.show()
Actual outcome
StopIteration Traceback (most recent call last)
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/IPython/core/formatters.py:340, in BaseFormatter.call(self, obj)
338 pass
339 else:
--> 340 return printer(obj)
341 # Finally look for special method names
342 method = get_real_method(obj, self.print_method)
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/IPython/core/pylabtools.py:152, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
149 from matplotlib.backend_bases import FigureCanvasBase
150 FigureCanvasBase(fig)
--> 152 fig.canvas.print_figure(bytes_io, **kw)
153 data = bytes_io.getvalue()
154 if fmt == 'svg':
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/backend_bases.py:2342, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2336 renderer = _get_renderer(
2337 self.figure,
2338 functools.partial(
2339 print_method, orientation=orientation)
2340 )
2341 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2342 self.figure.draw(renderer)
2344 if bbox_inches:
2345 if bbox_inches == "tight":
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/artist.py:95, in _finalize_rasterization..draw_wrapper(artist, renderer, *args, **kwargs)
93 @wraps(draw)
94 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 95 result = draw(artist, renderer, *args, **kwargs)
96 if renderer._rasterizing:
97 renderer.stop_rasterizing()
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/artist.py:72, in allow_rasterization..draw_wrapper(artist, renderer)
69 if artist.get_agg_filter() is not None:
70 renderer.start_filter()
---> 72 return draw(artist, renderer)
73 finally:
74 if artist.get_agg_filter() is not None:
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/figure.py:3140, in Figure.draw(self, renderer)
3137 # ValueError can occur when resizing a window.
3139 self.patch.draw(renderer)
-> 3140 mimage._draw_list_compositing_images(
3141 renderer, self, artists, self.suppressComposite)
3143 for sfig in self.subfigs:
3144 sfig.draw(renderer)
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
129 if not_composite or not has_images:
130 for a in artists:
--> 131 a.draw(renderer)
132 else:
133 # Composite any adjacent images together
134 image_group = []
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/artist.py:39, in _prevent_rasterization..draw_wrapper(artist, renderer, *args, **kwargs)
36 renderer.stop_rasterizing()
37 renderer._rasterizing = False
---> 39 return draw(artist, renderer, *args, **kwargs)
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/projections/polar.py:1037, in PolarAxes.draw(self, renderer)
1034 self.yaxis.reset_ticks()
1035 self.yaxis.set_clip_path(self.patch)
-> 1037 super().draw(renderer)
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/artist.py:72, in allow_rasterization..draw_wrapper(artist, renderer)
69 if artist.get_agg_filter() is not None:
70 renderer.start_filter()
---> 72 return draw(artist, renderer)
73 finally:
74 if artist.get_agg_filter() is not None:
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/axes/_base.py:3064, in _AxesBase.draw(self, renderer)
3061 if artists_rasterized:
3062 _draw_rasterized(self.figure, artists_rasterized, renderer)
-> 3064 mimage._draw_list_compositing_images(
3065 renderer, self, artists, self.figure.suppressComposite)
3067 renderer.close_group('axes')
3068 self.stale = False
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
129 if not_composite or not has_images:
130 for a in artists:
--> 131 a.draw(renderer)
132 else:
133 # Composite any adjacent images together
134 image_group = []
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/artist.py:72, in allow_rasterization..draw_wrapper(artist, renderer)
69 if artist.get_agg_filter() is not None:
70 renderer.start_filter()
---> 72 return draw(artist, renderer)
73 finally:
74 if artist.get_agg_filter() is not None:
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/text.py:2032, in Annotation.draw(self, renderer)
2030 if self.arrow_patch.figure is None and self.figure is not None:
2031 self.arrow_patch.figure = self.figure
-> 2032 self.arrow_patch.draw(renderer)
2033 # Draw text, including FancyBboxPatch, after FancyArrowPatch.
2034 # Otherwise, a wedge arrowstyle can land partly on top of the Bbox.
2035 Text.draw(self, renderer)
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/artist.py:39, in _prevent_rasterization..draw_wrapper(artist, renderer, *args, **kwargs)
36 renderer.stop_rasterizing()
37 renderer._rasterizing = False
---> 39 return draw(artist, renderer, *args, **kwargs)
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/patches.py:4352, in FancyArrowPatch.draw(self, renderer)
4348 # FIXME: dpi_cor is for the dpi-dependency of the linewidth. There
4349 # could be room for improvement. Maybe _get_path_in_displaycoord could
4350 # take a renderer argument, but get_path should be adapted too.
4351 self._dpi_cor = renderer.points_to_pixels(1.)
-> 4352 path, fillable = self._get_path_in_displaycoord()
4354 if not np.iterable(fillable):
4355 path = [path]
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/patches.py:4327, in FancyArrowPatch._get_path_in_displaycoord(self)
4325 posB = self._convert_xy_units(self._posA_posB[1])
4326 (posA, posB) = self.get_transform().transform((posA, posB))
-> 4327 _path = self.get_connectionstyle()(posA, posB,
4328 patchA=self.patchA,
4329 patchB=self.patchB,
4330 shrinkA=self.shrinkA * dpi_cor,
4331 shrinkB=self.shrinkB * dpi_cor
4332 )
4333 else:
4334 _path = self.get_transform().transform_path(self._path_original)
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/patches.py:2732, in ConnectionStyle._Base.call(self, posA, posB, shrinkA, shrinkB, patchA, patchB)
2726 path = self.connect(posA, posB)
2727 path = self._clip(
2728 path,
2729 self._in_patch(patchA) if patchA else None,
2730 self._in_patch(patchB) if patchB else None,
2731 )
-> 2732 path = self._clip(
2733 path,
2734 inside_circle(*path.vertices[0], shrinkA) if shrinkA else None,
2735 inside_circle(*path.vertices[-1], shrinkB) if shrinkB else None
2736 )
2737 return path
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/patches.py:2710, in ConnectionStyle._Base._clip(self, path, in_start, in_stop)
2708 if in_start:
2709 try:
-> 2710 _, path = split_path_inout(path, in_start)
2711 except ValueError:
2712 pass
File ~/.conda/envs/graph_svd/lib/python3.11/site-packages/matplotlib/bezier.py:351, in split_path_inout(path, inside, tolerance, reorder_inout)
348 from .path import Path
349 path_iter = path.iter_segments()
--> 351 ctl_points, command = next(path_iter)
352 begin_inside = inside(ctl_points[-2:]) # true if begin point is inside
354 ctl_points_old = ctl_points
StopIteration:
Expected outcome
Either a more descriptive error that the annotation arrow falls outside the axes limits or the image:
Additional information
This behaviour is quite erratic because the plot works fine if I 1) use different values for r, 2) comment the ax.set_rticks(...) line, 3) comment ax.annotate(...), 4) comment ax.scatter(an_theta, an_r), etc. The stacktrace seems to imply that the error occurs when the annotation arrow is being drawn.
Someone on SO suggested that the problem might be due to the Axes limits being set when setting the rticks. Indeed, ax.set_rlim(0, 1)
produces the expected plot. However, I'd then expect ax.set_rticks([0, 1, 2, 3])
to also make it work, but that does not seem to be the case.
Operating system
Almalinux 8.6
Matplotlib Version
3.7.1
Matplotlib Backend
module://matplotlib_inline.backend_inline
Python version
3.11.4
Jupyter version
4.2.5
Installation
conda