| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
a SpanSelector created with useblit=True causes an error when trying to save the figure as SVG. Reproducer is below, though I haven't tested all backends (only Agg and qtagg; both fail) or all output formats (only SVG, PDF, and PNG; PNG works and the other two don't). useblit=False allows SVG and PDF saving to work.
first reported at mne-tools/mne-python#13242 by @AasmaGupta, who did much of the initial isolation work. Tested on MPL 3.10.1 on Linux.
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
def onselect(xmin, xmax, **kwargs):
pass
fig, ax = plt.subplots()
ax.plot(np.random.randn(100, 3))
span_selector = SpanSelector(
ax,
onselect,
"horizontal",
useblit=True,
)
fig.savefig("test.svg") # 'FigureCanvasSVG' object has no attribute 'copy_from_bbox'---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
File ~/Desktop/svgmwe.py:22
14 ax.plot(np.random.randn(100, 3))
15 span_selector = SpanSelector(
16 ax,
17 onselect,
18 "horizontal",
19 useblit=True,
20 )
---> 22 fig.savefig("test.svg") # 'FigureCanvasSVG' object has no attribute 'copy_from_bbox'
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/figure.py:3490, in Figure.savefig(self, fname, transparent, **kwargs)
3488 for ax in self.axes:
3489 _recursively_make_axes_transparent(stack, ax)
-> 3490 self.canvas.print_figure(fname, **kwargs)
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/backend_bases.py:2184, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2180 try:
2181 # _get_renderer may change the figure dpi (as vector formats
2182 # force the figure dpi to 72), so we need to set it again here.
2183 with cbook._setattr_cm(self.figure, dpi=dpi):
-> 2184 result = print_method(
2185 filename,
2186 facecolor=facecolor,
2187 edgecolor=edgecolor,
2188 orientation=orientation,
2189 bbox_inches_restore=_bbox_inches_restore,
2190 **kwargs)
2191 finally:
2192 if bbox_inches and restore_bbox:
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/backend_bases.py:2040, in FigureCanvasBase._switch_canvas_and_return_print_method.<locals>.<lambda>(*args, **kwargs)
2036 optional_kws = { # Passed by print_figure for other renderers.
2037 "dpi", "facecolor", "edgecolor", "orientation",
2038 "bbox_inches_restore"}
2039 skip = optional_kws - {*inspect.signature(meth).parameters}
-> 2040 print_method = functools.wraps(meth)(lambda *args, **kwargs: meth(
2041 *args, **{k: v for k, v in kwargs.items() if k not in skip}))
2042 else: # Let third-parties do as they see fit.
2043 print_method = meth
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/backends/backend_svg.py:1351, in FigureCanvasSVG.print_svg(self, filename, bbox_inches_restore, metadata)
1346 w, h = width * 72, height * 72
1347 renderer = MixedModeRenderer(
1348 self.figure, width, height, dpi,
1349 RendererSVG(w, h, fh, image_dpi=dpi, metadata=metadata),
1350 bbox_inches_restore=bbox_inches_restore)
-> 1351 self.figure.draw(renderer)
1352 renderer.finalize()
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
92 @wraps(draw)
93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94 result = draw(artist, renderer, *args, **kwargs)
95 if renderer._rasterizing:
96 renderer.stop_rasterizing()
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
68 if artist.get_agg_filter() is not None:
69 renderer.start_filter()
---> 71 return draw(artist, renderer)
72 finally:
73 if artist.get_agg_filter() is not None:
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/figure.py:3264, in Figure.draw(self, renderer)
3261 finally:
3262 self.stale = False
-> 3264 DrawEvent("draw_event", self.canvas, renderer)._process()
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/backend_bases.py:1189, in Event._process(self)
1187 def _process(self):
1188 """Process this event on ``self.canvas``, then unset ``guiEvent``."""
-> 1189 self.canvas.callbacks.process(self.name, self)
1190 self.guiEvent = None
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/cbook.py:366, in CallbackRegistry.process(self, s, *args, **kwargs)
364 except Exception as exc:
365 if self.exception_handler is not None:
--> 366 self.exception_handler(exc)
367 else:
368 raise
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/cbook.py:110, in _exception_printer(exc)
108 def _exception_printer(exc):
109 if _get_running_interactive_framework() in ["headless", None]:
--> 110 raise exc
111 else:
112 traceback.print_exc()
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/cbook.py:361, in CallbackRegistry.process(self, s, *args, **kwargs)
359 if func is not None:
360 try:
--> 361 func(*args, **kwargs)
362 # this does not capture KeyboardInterrupt, SystemExit,
363 # and GeneratorExit
364 except Exception as exc:
File /opt/mambaforge/envs/mnedev/lib/python3.13/site-packages/matplotlib/widgets.py:2167, in _SelectorWidget.update_background(self, event)
2165 stack.enter_context(artist._cm_set(visible=False))
2166 self.canvas.draw()
-> 2167 self.background = self.canvas.copy_from_bbox(self.ax.bbox)
2168 if needs_redraw:
2169 for artist in artists:
AttributeError: 'FigureCanvasSVG' object has no attribute 'copy_from_bbox'
Figure is saved normally.
No response
Linux
3.10.1
Agg and QtAgg
3.13.2
n/a
conda
| Back | FazBrowse Home | New Git URL |
Activity
AasmaGupta commented on Aug 29, 2025
Thanks so much, @drammock, for mentioning my work and giving credit! I did much of the initial isolation work on this issue, and I really appreciate it being acknowledged.
For anyone who wants an even smaller minimal reproducible snippet, here’s one I came up with:
import numpy as np import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib.widgets import SpanSelector fig, ax = plt.subplots() ax.plot(np.random.randn(100, 3)) ax._span = SpanSelector(ax, lambda xmin, xmax: None, "horizontal", useblit=True) fig.savefig("test.svg") # triggers AttributeErrorThis reproduces the error with useblit=True. Setting useblit=False avoids the issue.
tacaswell commented on Aug 29, 2025
We track the state of if the draw we are currently doing is for saving to disk and if so should skip all of the blitting, we are missing using that guard here.