Contouring#
Developer: Andrew Nolan (@andrewdnolan)
Overview#
For complete visualization capabilities in mosaic, we need to be able to create contour and filled contour plots. Unfortunately, we are unable to use the existing matplotlib.pyplot.contour and matplotlib.pyplot.contourf functions, due to two matplotlib requirements:
That the
XandYcoordinates arrays represent a regularly spaces, structured grid.That the
Zdata being contoured be coincidental with theXandYcoordinates on the mesh.
The above requirements are enforced, because “under the hood” matplotlib uses the contourpy packages, which is a C++ implementation of the marching squares algorithm. The unstrucuted nature of MPAS meshes prevents (1) from ever being satisfied. Because MPAS meshes represent control volumes, data are not defined at discrete “nodes” but over a control volumes, therefore preventing (2) from being easily satisfied. The analogous marching triangles algorithm could work for out unstructured data, but contourpy has not implemented it. From a cursory look, there do not seem to be any implementations of marching triangles, with a python interface, as mature and well mainted as the marching squares algorithm within contourpy.
Instead, we’ve opted to follow Andrew Robert’s Ridgepack MATLAB package and use the mesh connectivity information to create contours of unstrucuted MPAS meshes. The implementation below follows Ridgepack, but we have opted to avoid a direct port of the MATLAB code. Instead we crib from the approach, but try to implement as much as possible using external python libraries to increase robustness and performance.
Rough Sketch of Implementation#
Let’s start by considering a small (\(20 \times 20\) cell) planar non-periodic MPAS mesh. The initial field we will consider will be a 2D gaussian with an amplitude (\(A\)) of \(2\) and standard deviation (\(\sigma\)) of \(0.15\):
ds = planar_hex_mesh(20)
gauss = gaussian(ds.xCell, ds.yCell)
descriptor = mosaic.Descriptor(ds)
Error in callback <function _draw_all_if_interactive at 0x7f4811a4ba00> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
276 def _draw_all_if_interactive() -> None:
277 if matplotlib.is_interactive():
--> 278 draw_all()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
129 for manager in cls.get_all_fig_managers():
130 if force or manager.canvas.figure.stale:
--> 131 manager.canvas.draw_idle()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
1891 if not self._is_idle_drawing:
1892 with self._idle_draw_cntx():
-> 1893 self.draw(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
379 # Acquire a lock on the shared font cache.
380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
381 else nullcontext()):
--> 382 self.figure.draw(self.renderer)
383 # A GUI class may be need to update a window using this draw, so
384 # don't forget to call the superclass.
385 super().draw()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:405, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
403 cbp_rspan, cbp_cspan = get_cb_parent_spans(cbax)
404 loc = cbax._colorbar_info['location']
--> 405 cbpos, cbbbox = get_pos_and_bbox(cbax, renderer)
406 if loc == 'right':
407 if cbp_cspan.stop == ss.colspan.stop:
408 # only increase if the colorbar is on the right edge
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4564, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4562 for axis in self._axis_map.values():
4563 if self.axison and axis.get_visible():
-> 4564 ba = martist._get_tightbbox_for_layout_only(axis, renderer)
4565 if ba:
4566 bb.append(ba)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1334, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
-> 1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:405, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
403 cbp_rspan, cbp_cspan = get_cb_parent_spans(cbax)
404 loc = cbax._colorbar_info['location']
--> 405 cbpos, cbbbox = get_pos_and_bbox(cbax, renderer)
406 if loc == 'right':
407 if cbp_cspan.stop == ss.colspan.stop:
408 # only increase if the colorbar is on the right edge
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4564, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4562 for axis in self._axis_map.values():
4563 if self.axison and axis.get_visible():
-> 4564 ba = martist._get_tightbbox_for_layout_only(axis, renderer)
4565 if ba:
4566 bb.append(ba)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1334, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
-> 1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
<Figure size 350x350 with 2 Axes>
Figure 1. Gaussian kernel with \(A=2\) and \(\sigma=0.15\) evaluated on a \(20\times20\) cell planar non-periodic MPAS mesh.
Now, let’s contour the 1.0 level. Like “Marching Squares” algorithm, we’ll start by creating a boolean mask of cells above and below the contour level.
mask = gauss < 1.0
Error in callback <function _draw_all_if_interactive at 0x7f4811a4ba00> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:250, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
249 try:
--> 250 report = subprocess.check_output(
251 command, cwd=cwd if cwd is not None else cls._texcache,
252 stderr=subprocess.STDOUT)
253 except FileNotFoundError as exc:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:472, in check_output(timeout, *popenargs, **kwargs)
470 kwargs['input'] = empty
--> 472 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
473 **kwargs).stdout
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:554, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
552 kwargs['stderr'] = PIPE
--> 554 with Popen(*popenargs, **kwargs) as process:
555 try:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:1038, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
1035 self.stderr = io.TextIOWrapper(self.stderr,
1036 encoding=encoding, errors=errors)
-> 1038 self._execute_child(args, executable, preexec_fn, close_fds,
1039 pass_fds, cwd, env,
1040 startupinfo, creationflags, shell,
1041 p2cread, p2cwrite,
1042 c2pread, c2pwrite,
1043 errread, errwrite,
1044 restore_signals,
1045 gid, gids, uid, umask,
1046 start_new_session, process_group)
1047 except:
1048 # Cleanup if the child failed starting.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:1989, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
1988 if err_filename is not None:
-> 1989 raise child_exception_type(errno_num, err_msg, err_filename)
1990 else:
FileNotFoundError: [Errno 2] No such file or directory: 'latex'
The above exception was the direct cause of the following exception:
RuntimeError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
276 def _draw_all_if_interactive() -> None:
277 if matplotlib.is_interactive():
--> 278 draw_all()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
129 for manager in cls.get_all_fig_managers():
130 if force or manager.canvas.figure.stale:
--> 131 manager.canvas.draw_idle()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
1891 if not self._is_idle_drawing:
1892 with self._idle_draw_cntx():
-> 1893 self.draw(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
379 # Acquire a lock on the shared font cache.
380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
381 else nullcontext()):
--> 382 self.figure.draw(self.renderer)
383 # A GUI class may be need to update a window using this draw, so
384 # don't forget to call the superclass.
385 super().draw()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4587, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4584 bbox_artists = self.get_default_bbox_extra_artists()
4586 for a in bbox_artists:
-> 4587 bbox = a.get_tightbbox(renderer)
4588 if (bbox is not None
4589 and 0 < bbox.width < np.inf
4590 and 0 < bbox.height < np.inf):
4591 bb.append(bbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/legend.py:1060, in Legend.get_tightbbox(self, renderer)
1058 def get_tightbbox(self, renderer=None):
1059 # docstring inherited
-> 1060 return self._legend_box.get_window_extent(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:367, in OffsetBox.get_window_extent(self, renderer)
365 if renderer is None:
366 renderer = self.get_figure(root=True)._get_renderer()
--> 367 bbox = self.get_bbox(renderer)
368 try: # Some subclasses redefine get_offset to take no args.
369 px, py = self.get_offset(bbox, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:797, in TextArea.get_bbox(self, renderer)
796 def get_bbox(self, renderer):
--> 797 _, h_, d_ = mtext._get_text_metrics_with_cache(
798 renderer, "lp", self._text._fontproperties,
799 ismath="TeX" if self._text.get_usetex() else False,
800 dpi=self.get_figure(root=True).dpi)
802 bbox, info, yd = self._text._get_layout(renderer)
803 w, h = bbox.size
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:364, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
362 if tex.strip() == '':
363 return 0, 0, 0
--> 364 dvifile = cls.make_dvi(tex, fontsize)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:293, in TexManager.make_dvi(cls, tex, fontsize)
290 with TemporaryDirectory(dir=dvifile.parent) as tmpdir:
291 Path(tmpdir, "file.tex").write_text(
292 cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 293 cls._run_checked_subprocess(
294 ["latex", "-interaction=nonstopmode", "--halt-on-error",
295 "file.tex"], tex, cwd=tmpdir)
296 Path(tmpdir, "file.dvi").replace(dvifile)
297 # Also move the tex source to the main cache directory, but
298 # only for backcompat.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:254, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
250 report = subprocess.check_output(
251 command, cwd=cwd if cwd is not None else cls._texcache,
252 stderr=subprocess.STDOUT)
253 except FileNotFoundError as exc:
--> 254 raise RuntimeError(
255 f'Failed to process string with tex because {command[0]} '
256 'could not be found') from exc
257 except subprocess.CalledProcessError as exc:
258 raise RuntimeError(
259 '{prog} was not able to process the following string:\n'
260 '{tex!r}\n\n'
(...) 267 exc=exc.output.decode('utf-8', 'backslashreplace'))
268 ) from None
RuntimeError: Failed to process string with tex because latex could not be found
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:250, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
249 try:
--> 250 report = subprocess.check_output(
251 command, cwd=cwd if cwd is not None else cls._texcache,
252 stderr=subprocess.STDOUT)
253 except FileNotFoundError as exc:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:472, in check_output(timeout, *popenargs, **kwargs)
470 kwargs['input'] = empty
--> 472 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
473 **kwargs).stdout
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:554, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
552 kwargs['stderr'] = PIPE
--> 554 with Popen(*popenargs, **kwargs) as process:
555 try:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:1038, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
1035 self.stderr = io.TextIOWrapper(self.stderr,
1036 encoding=encoding, errors=errors)
-> 1038 self._execute_child(args, executable, preexec_fn, close_fds,
1039 pass_fds, cwd, env,
1040 startupinfo, creationflags, shell,
1041 p2cread, p2cwrite,
1042 c2pread, c2pwrite,
1043 errread, errwrite,
1044 restore_signals,
1045 gid, gids, uid, umask,
1046 start_new_session, process_group)
1047 except:
1048 # Cleanup if the child failed starting.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:1989, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
1988 if err_filename is not None:
-> 1989 raise child_exception_type(errno_num, err_msg, err_filename)
1990 else:
FileNotFoundError: [Errno 2] No such file or directory: 'latex'
The above exception was the direct cause of the following exception:
RuntimeError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4587, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4584 bbox_artists = self.get_default_bbox_extra_artists()
4586 for a in bbox_artists:
-> 4587 bbox = a.get_tightbbox(renderer)
4588 if (bbox is not None
4589 and 0 < bbox.width < np.inf
4590 and 0 < bbox.height < np.inf):
4591 bb.append(bbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/legend.py:1060, in Legend.get_tightbbox(self, renderer)
1058 def get_tightbbox(self, renderer=None):
1059 # docstring inherited
-> 1060 return self._legend_box.get_window_extent(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:367, in OffsetBox.get_window_extent(self, renderer)
365 if renderer is None:
366 renderer = self.get_figure(root=True)._get_renderer()
--> 367 bbox = self.get_bbox(renderer)
368 try: # Some subclasses redefine get_offset to take no args.
369 px, py = self.get_offset(bbox, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:797, in TextArea.get_bbox(self, renderer)
796 def get_bbox(self, renderer):
--> 797 _, h_, d_ = mtext._get_text_metrics_with_cache(
798 renderer, "lp", self._text._fontproperties,
799 ismath="TeX" if self._text.get_usetex() else False,
800 dpi=self.get_figure(root=True).dpi)
802 bbox, info, yd = self._text._get_layout(renderer)
803 w, h = bbox.size
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:364, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
362 if tex.strip() == '':
363 return 0, 0, 0
--> 364 dvifile = cls.make_dvi(tex, fontsize)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:293, in TexManager.make_dvi(cls, tex, fontsize)
290 with TemporaryDirectory(dir=dvifile.parent) as tmpdir:
291 Path(tmpdir, "file.tex").write_text(
292 cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 293 cls._run_checked_subprocess(
294 ["latex", "-interaction=nonstopmode", "--halt-on-error",
295 "file.tex"], tex, cwd=tmpdir)
296 Path(tmpdir, "file.dvi").replace(dvifile)
297 # Also move the tex source to the main cache directory, but
298 # only for backcompat.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:254, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
250 report = subprocess.check_output(
251 command, cwd=cwd if cwd is not None else cls._texcache,
252 stderr=subprocess.STDOUT)
253 except FileNotFoundError as exc:
--> 254 raise RuntimeError(
255 f'Failed to process string with tex because {command[0]} '
256 'could not be found') from exc
257 except subprocess.CalledProcessError as exc:
258 raise RuntimeError(
259 '{prog} was not able to process the following string:\n'
260 '{tex!r}\n\n'
(...) 267 exc=exc.output.decode('utf-8', 'backslashreplace'))
268 ) from None
RuntimeError: Failed to process string with tex because latex could not be found
<Figure size 350x350 with 1 Axes>
Figure 2. Boolean mask of cells with a value greater than \(1\) for the field plotted above (Figure 1). Gray cells are above (True) and white cells are below (False) the contour level.
From examining the boolean mask, plotted on the native mesh, it becomes clear that one way to display contour boundary is to use the edges of the mesh where the mask is true on one side and false on the other. To identify the edges where this is true, we’ll use the “exculusive or” (i.e. \(\mathrm{XOR}\)) operation along the second dimension of the \(\mathrm{cellsOnEdge}\) array, which is of size \(\mathrm{nEdges} \times 2\).
Error in callback <function _draw_all_if_interactive at 0x7f4811a4ba00> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:250, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
249 try:
--> 250 report = subprocess.check_output(
251 command, cwd=cwd if cwd is not None else cls._texcache,
252 stderr=subprocess.STDOUT)
253 except FileNotFoundError as exc:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:472, in check_output(timeout, *popenargs, **kwargs)
470 kwargs['input'] = empty
--> 472 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
473 **kwargs).stdout
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:554, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
552 kwargs['stderr'] = PIPE
--> 554 with Popen(*popenargs, **kwargs) as process:
555 try:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:1038, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
1035 self.stderr = io.TextIOWrapper(self.stderr,
1036 encoding=encoding, errors=errors)
-> 1038 self._execute_child(args, executable, preexec_fn, close_fds,
1039 pass_fds, cwd, env,
1040 startupinfo, creationflags, shell,
1041 p2cread, p2cwrite,
1042 c2pread, c2pwrite,
1043 errread, errwrite,
1044 restore_signals,
1045 gid, gids, uid, umask,
1046 start_new_session, process_group)
1047 except:
1048 # Cleanup if the child failed starting.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:1989, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
1988 if err_filename is not None:
-> 1989 raise child_exception_type(errno_num, err_msg, err_filename)
1990 else:
FileNotFoundError: [Errno 2] No such file or directory: 'latex'
The above exception was the direct cause of the following exception:
RuntimeError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
276 def _draw_all_if_interactive() -> None:
277 if matplotlib.is_interactive():
--> 278 draw_all()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
129 for manager in cls.get_all_fig_managers():
130 if force or manager.canvas.figure.stale:
--> 131 manager.canvas.draw_idle()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
1891 if not self._is_idle_drawing:
1892 with self._idle_draw_cntx():
-> 1893 self.draw(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
379 # Acquire a lock on the shared font cache.
380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
381 else nullcontext()):
--> 382 self.figure.draw(self.renderer)
383 # A GUI class may be need to update a window using this draw, so
384 # don't forget to call the superclass.
385 super().draw()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4587, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4584 bbox_artists = self.get_default_bbox_extra_artists()
4586 for a in bbox_artists:
-> 4587 bbox = a.get_tightbbox(renderer)
4588 if (bbox is not None
4589 and 0 < bbox.width < np.inf
4590 and 0 < bbox.height < np.inf):
4591 bb.append(bbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/legend.py:1060, in Legend.get_tightbbox(self, renderer)
1058 def get_tightbbox(self, renderer=None):
1059 # docstring inherited
-> 1060 return self._legend_box.get_window_extent(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:367, in OffsetBox.get_window_extent(self, renderer)
365 if renderer is None:
366 renderer = self.get_figure(root=True)._get_renderer()
--> 367 bbox = self.get_bbox(renderer)
368 try: # Some subclasses redefine get_offset to take no args.
369 px, py = self.get_offset(bbox, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:797, in TextArea.get_bbox(self, renderer)
796 def get_bbox(self, renderer):
--> 797 _, h_, d_ = mtext._get_text_metrics_with_cache(
798 renderer, "lp", self._text._fontproperties,
799 ismath="TeX" if self._text.get_usetex() else False,
800 dpi=self.get_figure(root=True).dpi)
802 bbox, info, yd = self._text._get_layout(renderer)
803 w, h = bbox.size
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:364, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
362 if tex.strip() == '':
363 return 0, 0, 0
--> 364 dvifile = cls.make_dvi(tex, fontsize)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:293, in TexManager.make_dvi(cls, tex, fontsize)
290 with TemporaryDirectory(dir=dvifile.parent) as tmpdir:
291 Path(tmpdir, "file.tex").write_text(
292 cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 293 cls._run_checked_subprocess(
294 ["latex", "-interaction=nonstopmode", "--halt-on-error",
295 "file.tex"], tex, cwd=tmpdir)
296 Path(tmpdir, "file.dvi").replace(dvifile)
297 # Also move the tex source to the main cache directory, but
298 # only for backcompat.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:254, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
250 report = subprocess.check_output(
251 command, cwd=cwd if cwd is not None else cls._texcache,
252 stderr=subprocess.STDOUT)
253 except FileNotFoundError as exc:
--> 254 raise RuntimeError(
255 f'Failed to process string with tex because {command[0]} '
256 'could not be found') from exc
257 except subprocess.CalledProcessError as exc:
258 raise RuntimeError(
259 '{prog} was not able to process the following string:\n'
260 '{tex!r}\n\n'
(...) 267 exc=exc.output.decode('utf-8', 'backslashreplace'))
268 ) from None
RuntimeError: Failed to process string with tex because latex could not be found
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:250, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
249 try:
--> 250 report = subprocess.check_output(
251 command, cwd=cwd if cwd is not None else cls._texcache,
252 stderr=subprocess.STDOUT)
253 except FileNotFoundError as exc:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:472, in check_output(timeout, *popenargs, **kwargs)
470 kwargs['input'] = empty
--> 472 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
473 **kwargs).stdout
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:554, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
552 kwargs['stderr'] = PIPE
--> 554 with Popen(*popenargs, **kwargs) as process:
555 try:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:1038, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
1035 self.stderr = io.TextIOWrapper(self.stderr,
1036 encoding=encoding, errors=errors)
-> 1038 self._execute_child(args, executable, preexec_fn, close_fds,
1039 pass_fds, cwd, env,
1040 startupinfo, creationflags, shell,
1041 p2cread, p2cwrite,
1042 c2pread, c2pwrite,
1043 errread, errwrite,
1044 restore_signals,
1045 gid, gids, uid, umask,
1046 start_new_session, process_group)
1047 except:
1048 # Cleanup if the child failed starting.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/subprocess.py:1989, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
1988 if err_filename is not None:
-> 1989 raise child_exception_type(errno_num, err_msg, err_filename)
1990 else:
FileNotFoundError: [Errno 2] No such file or directory: 'latex'
The above exception was the direct cause of the following exception:
RuntimeError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4587, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4584 bbox_artists = self.get_default_bbox_extra_artists()
4586 for a in bbox_artists:
-> 4587 bbox = a.get_tightbbox(renderer)
4588 if (bbox is not None
4589 and 0 < bbox.width < np.inf
4590 and 0 < bbox.height < np.inf):
4591 bb.append(bbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/legend.py:1060, in Legend.get_tightbbox(self, renderer)
1058 def get_tightbbox(self, renderer=None):
1059 # docstring inherited
-> 1060 return self._legend_box.get_window_extent(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:367, in OffsetBox.get_window_extent(self, renderer)
365 if renderer is None:
366 renderer = self.get_figure(root=True)._get_renderer()
--> 367 bbox = self.get_bbox(renderer)
368 try: # Some subclasses redefine get_offset to take no args.
369 px, py = self.get_offset(bbox, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:797, in TextArea.get_bbox(self, renderer)
796 def get_bbox(self, renderer):
--> 797 _, h_, d_ = mtext._get_text_metrics_with_cache(
798 renderer, "lp", self._text._fontproperties,
799 ismath="TeX" if self._text.get_usetex() else False,
800 dpi=self.get_figure(root=True).dpi)
802 bbox, info, yd = self._text._get_layout(renderer)
803 w, h = bbox.size
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:364, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
362 if tex.strip() == '':
363 return 0, 0, 0
--> 364 dvifile = cls.make_dvi(tex, fontsize)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:293, in TexManager.make_dvi(cls, tex, fontsize)
290 with TemporaryDirectory(dir=dvifile.parent) as tmpdir:
291 Path(tmpdir, "file.tex").write_text(
292 cls._get_tex_source(tex, fontsize), encoding='utf-8')
--> 293 cls._run_checked_subprocess(
294 ["latex", "-interaction=nonstopmode", "--halt-on-error",
295 "file.tex"], tex, cwd=tmpdir)
296 Path(tmpdir, "file.dvi").replace(dvifile)
297 # Also move the tex source to the main cache directory, but
298 # only for backcompat.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:254, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
250 report = subprocess.check_output(
251 command, cwd=cwd if cwd is not None else cls._texcache,
252 stderr=subprocess.STDOUT)
253 except FileNotFoundError as exc:
--> 254 raise RuntimeError(
255 f'Failed to process string with tex because {command[0]} '
256 'could not be found') from exc
257 except subprocess.CalledProcessError as exc:
258 raise RuntimeError(
259 '{prog} was not able to process the following string:\n'
260 '{tex!r}\n\n'
(...) 267 exc=exc.output.decode('utf-8', 'backslashreplace'))
268 ) from None
RuntimeError: Failed to process string with tex because latex could not be found
<Figure size 350x350 with 1 Axes>
Figure 3. Boolean mask of cells with a value greater than \(1\), with edges corresponding to the contour boundary denoted in blue.
Unfortunately it’s not quite that simple. While we’ve identified the edges that correspond to the contour boundary, the order of those edges are quite important for plotting.
Error in callback <function _draw_all_if_interactive at 0x7f4811a4ba00> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
276 def _draw_all_if_interactive() -> None:
277 if matplotlib.is_interactive():
--> 278 draw_all()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
129 for manager in cls.get_all_fig_managers():
130 if force or manager.canvas.figure.stale:
--> 131 manager.canvas.draw_idle()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
1891 if not self._is_idle_drawing:
1892 with self._idle_draw_cntx():
-> 1893 self.draw(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
379 # Acquire a lock on the shared font cache.
380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
381 else nullcontext()):
--> 382 self.figure.draw(self.renderer)
383 # A GUI class may be need to update a window using this draw, so
384 # don't forget to call the superclass.
385 super().draw()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4587, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4584 bbox_artists = self.get_default_bbox_extra_artists()
4586 for a in bbox_artists:
-> 4587 bbox = a.get_tightbbox(renderer)
4588 if (bbox is not None
4589 and 0 < bbox.width < np.inf
4590 and 0 < bbox.height < np.inf):
4591 bb.append(bbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/legend.py:1060, in Legend.get_tightbbox(self, renderer)
1058 def get_tightbbox(self, renderer=None):
1059 # docstring inherited
-> 1060 return self._legend_box.get_window_extent(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:367, in OffsetBox.get_window_extent(self, renderer)
365 if renderer is None:
366 renderer = self.get_figure(root=True)._get_renderer()
--> 367 bbox = self.get_bbox(renderer)
368 try: # Some subclasses redefine get_offset to take no args.
369 px, py = self.get_offset(bbox, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:797, in TextArea.get_bbox(self, renderer)
796 def get_bbox(self, renderer):
--> 797 _, h_, d_ = mtext._get_text_metrics_with_cache(
798 renderer, "lp", self._text._fontproperties,
799 ismath="TeX" if self._text.get_usetex() else False,
800 dpi=self.get_figure(root=True).dpi)
802 bbox, info, yd = self._text._get_layout(renderer)
803 w, h = bbox.size
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4587, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4584 bbox_artists = self.get_default_bbox_extra_artists()
4586 for a in bbox_artists:
-> 4587 bbox = a.get_tightbbox(renderer)
4588 if (bbox is not None
4589 and 0 < bbox.width < np.inf
4590 and 0 < bbox.height < np.inf):
4591 bb.append(bbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/legend.py:1060, in Legend.get_tightbbox(self, renderer)
1058 def get_tightbbox(self, renderer=None):
1059 # docstring inherited
-> 1060 return self._legend_box.get_window_extent(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:367, in OffsetBox.get_window_extent(self, renderer)
365 if renderer is None:
366 renderer = self.get_figure(root=True)._get_renderer()
--> 367 bbox = self.get_bbox(renderer)
368 try: # Some subclasses redefine get_offset to take no args.
369 px, py = self.get_offset(bbox, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:797, in TextArea.get_bbox(self, renderer)
796 def get_bbox(self, renderer):
--> 797 _, h_, d_ = mtext._get_text_metrics_with_cache(
798 renderer, "lp", self._text._fontproperties,
799 ismath="TeX" if self._text.get_usetex() else False,
800 dpi=self.get_figure(root=True).dpi)
802 bbox, info, yd = self._text._get_layout(renderer)
803 w, h = bbox.size
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
<Figure size 735x350 with 4 Axes>
Figure 4. (Left) Contour boundary edges color coded by their index in the boundary edge list. (Right) An attempt to plot the boundary edges as polygon. Because of the near random order of edges around the contour, the resulting polygon is self-intersecting.
Unstrucuted Mesh Contours as Graphs#
Using the \(\mathrm{XOR}\) operator and the \(\mathrm{cellsOnEdge}\) connectivity array, we have identified the edges corresponding to the contour boundary. These contour edges are useful, but for plotting purposes what we are really interested in is an ordered sequence of vertices so that we can draw them as a continuous line. We can use the \(\mathrm{verticesOnEdge}\) connectivity array (\(\mathrm{nEdges} \times 2\)) for the subset of edges that correspond to the contour boundary to get the information we need. This subset of \(\mathrm{verticesOnEdge}\) naturally defines a graph, where mesh vertices become graph nodes and contour edges become graph edges.
The key structural property of this graph is that every node has degree at most 2; each contour vertex is shared by at most two contour edges (the one “entering” and the one “leaving”). This constraint means every connected component is one of exactly two topologies:
Path graph: an open arc whose two degree-1 endpoints lie on the domain boundary. The contour crosses the mesh boundary.
Cycle graph: a closed loop entirely within the domain interior where every node has degree exactly 2.
Because the maximum degree is 2, traversal reduces to a simple linear chain walk: at each step there is at most one unvisited neighbor. For a path, we start at one of the two degree-1 endpoints and walk to the other. For a cycle, we start at any node and walk until no unvisited neighbors remain, then close the loop by appending the start node.
This is implemented in the custom ContourGraph class in mosaic/contour.py. The walk() method performs the chain traversal, and components() uses an iterative depth-first search to enumerate connected components. No general graph library is required at runtime — NetworkX is available only as an optional testing utility via ContourGraph.to_networkx().
Error in callback <function _draw_all_if_interactive at 0x7f4811a4ba00> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
276 def _draw_all_if_interactive() -> None:
277 if matplotlib.is_interactive():
--> 278 draw_all()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
129 for manager in cls.get_all_fig_managers():
130 if force or manager.canvas.figure.stale:
--> 131 manager.canvas.draw_idle()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
1891 if not self._is_idle_drawing:
1892 with self._idle_draw_cntx():
-> 1893 self.draw(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
379 # Acquire a lock on the shared font cache.
380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
381 else nullcontext()):
--> 382 self.figure.draw(self.renderer)
383 # A GUI class may be need to update a window using this draw, so
384 # don't forget to call the superclass.
385 super().draw()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4587, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4584 bbox_artists = self.get_default_bbox_extra_artists()
4586 for a in bbox_artists:
-> 4587 bbox = a.get_tightbbox(renderer)
4588 if (bbox is not None
4589 and 0 < bbox.width < np.inf
4590 and 0 < bbox.height < np.inf):
4591 bb.append(bbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/legend.py:1060, in Legend.get_tightbbox(self, renderer)
1058 def get_tightbbox(self, renderer=None):
1059 # docstring inherited
-> 1060 return self._legend_box.get_window_extent(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:367, in OffsetBox.get_window_extent(self, renderer)
365 if renderer is None:
366 renderer = self.get_figure(root=True)._get_renderer()
--> 367 bbox = self.get_bbox(renderer)
368 try: # Some subclasses redefine get_offset to take no args.
369 px, py = self.get_offset(bbox, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:797, in TextArea.get_bbox(self, renderer)
796 def get_bbox(self, renderer):
--> 797 _, h_, d_ = mtext._get_text_metrics_with_cache(
798 renderer, "lp", self._text._fontproperties,
799 ismath="TeX" if self._text.get_usetex() else False,
800 dpi=self.get_figure(root=True).dpi)
802 bbox, info, yd = self._text._get_layout(renderer)
803 w, h = bbox.size
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4587, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4584 bbox_artists = self.get_default_bbox_extra_artists()
4586 for a in bbox_artists:
-> 4587 bbox = a.get_tightbbox(renderer)
4588 if (bbox is not None
4589 and 0 < bbox.width < np.inf
4590 and 0 < bbox.height < np.inf):
4591 bb.append(bbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/legend.py:1060, in Legend.get_tightbbox(self, renderer)
1058 def get_tightbbox(self, renderer=None):
1059 # docstring inherited
-> 1060 return self._legend_box.get_window_extent(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:367, in OffsetBox.get_window_extent(self, renderer)
365 if renderer is None:
366 renderer = self.get_figure(root=True)._get_renderer()
--> 367 bbox = self.get_bbox(renderer)
368 try: # Some subclasses redefine get_offset to take no args.
369 px, py = self.get_offset(bbox, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:460, in VPacker._get_bbox_and_child_offsets(self, renderer)
457 if isinstance(c, PackerBase) and c.mode == "expand":
458 c.set_width(self.width)
--> 460 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
461 (x0, x1), xoffsets = _get_aligned_offsets(
462 [bbox.intervalx for bbox in bboxes], self.width, self.align)
463 height, yoffsets = _get_packed_offsets(
464 [bbox.height for bbox in bboxes], self.height, sep, self.mode)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:360, in OffsetBox.get_bbox(self, renderer)
358 def get_bbox(self, renderer):
359 """Return the bbox of the offsetbox, ignoring parent offsets."""
--> 360 bbox, offsets = self._get_bbox_and_child_offsets(renderer)
361 return bbox
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:493, in HPacker._get_bbox_and_child_offsets(self, renderer)
490 pad = self.pad * dpicor
491 sep = self.sep * dpicor
--> 493 bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
494 if not bboxes:
495 return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/offsetbox.py:797, in TextArea.get_bbox(self, renderer)
796 def get_bbox(self, renderer):
--> 797 _, h_, d_ = mtext._get_text_metrics_with_cache(
798 renderer, "lp", self._text._fontproperties,
799 ismath="TeX" if self._text.get_usetex() else False,
800 dpi=self.get_figure(root=True).dpi)
802 bbox, info, yd = self._text._get_layout(renderer)
803 w, h = bbox.size
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
<Figure size 735x350 with 4 Axes>
Figure 5. (Left) Ordered contour boundary edges color-coded by their index in the boundary edge list. (Right) Ordered contour boundary edges as a polygon. Because the graph traversal produces a sorted vertex sequence, the resulting polygon is valid (i.e. not self-intersecting).
With the ordered vertex sequence from the graph traversal, we can now plot the contour boundary as a polygon. This is the basic functionality we need to plot unstructured contour boundaries in matplotlib.
Before we move on, let’s quickly look at another way to delineate the contour boundary.
Interface#
With a basic sketch of the algorithmic approach outlined above, let’s now discuss how we will implement this in practice and how we envision a user interacting with it.
The goal is to provide contouring ability in mosaic where the interface to our contouring functions are as close as possible to matplotlib interface. Such that users can capitialize on their previous experience with matplotlib and seamlessly contour directly on the unstructed mesh.
Ideally, we will implement something like:
mosaic.contour(ax, descriptor, field, ...)
mosaic.contourf(ax, descriptor, field, ...)
where ... are the exact same keyword positional and key-word arguments as the matplotlib comensurate functions.
Unfilled Contours#
We’ll begin with unfilled contours (i.e. mosaic.contour), as they are simpler task.
Instead of such a simple gaussian field as we consider before, let’s work with something more complicated:
\(A\) is the amplitude
\(N\) is the period
\(L_{\rm x}\) and \(L_{\rm y}\) as the domain lengths in the \(x\) and \(y\) directions, respectively
We’ll also slightly increase the size of the mesh to be \(50 \times 50\) cells.
ds = planar_hex_mesh(100)
field = sinusoid(ds.xCell, ds.yCell)
descriptor = mosaic.Descriptor(ds)
Error in callback <function _draw_all_if_interactive at 0x7f4811a4ba00> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
276 def _draw_all_if_interactive() -> None:
277 if matplotlib.is_interactive():
--> 278 draw_all()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
129 for manager in cls.get_all_fig_managers():
130 if force or manager.canvas.figure.stale:
--> 131 manager.canvas.draw_idle()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
1891 if not self._is_idle_drawing:
1892 with self._idle_draw_cntx():
-> 1893 self.draw(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
379 # Acquire a lock on the shared font cache.
380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
381 else nullcontext()):
--> 382 self.figure.draw(self.renderer)
383 # A GUI class may be need to update a window using this draw, so
384 # don't forget to call the superclass.
385 super().draw()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:405, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
403 cbp_rspan, cbp_cspan = get_cb_parent_spans(cbax)
404 loc = cbax._colorbar_info['location']
--> 405 cbpos, cbbbox = get_pos_and_bbox(cbax, renderer)
406 if loc == 'right':
407 if cbp_cspan.stop == ss.colspan.stop:
408 # only increase if the colorbar is on the right edge
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4564, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4562 for axis in self._axis_map.values():
4563 if self.axison and axis.get_visible():
-> 4564 ba = martist._get_tightbbox_for_layout_only(axis, renderer)
4565 if ba:
4566 bb.append(ba)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1334, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
-> 1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:405, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
403 cbp_rspan, cbp_cspan = get_cb_parent_spans(cbax)
404 loc = cbax._colorbar_info['location']
--> 405 cbpos, cbbbox = get_pos_and_bbox(cbax, renderer)
406 if loc == 'right':
407 if cbp_cspan.stop == ss.colspan.stop:
408 # only increase if the colorbar is on the right edge
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4564, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4562 for axis in self._axis_map.values():
4563 if self.axison and axis.get_visible():
-> 4564 ba = martist._get_tightbbox_for_layout_only(axis, renderer)
4565 if ba:
4566 bb.append(ba)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1334, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
-> 1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
<Figure size 350x350 with 2 Axes>
Figure 6. Checkerboard field, evaluated on a \(100 \times 100\) cell mesh.
A first step, let’s create the boolean
mask = field < 0.0
Figure 7. (Left) Boolean mask of \(f(x, y) < 0.0\) evaluated on the MPAS mesh. (Right) The same checkerboard field, evaluated and contoured on a regular quadrilateral mesh. Blue lines are the contour lines and grey is the filled contour area.
Boundaries
We can see from the matplotlib.pyplot.contour example above (RHS of Figure 7), that contour lines should be discontinuous when they intersect with a boundary. For example, while the plotted mask follows the plot boundary, the contour lines abruptly end. They do not from closed loops by using the boundary. We’ll mimic this behavior in mosaic.
Error in callback <function _draw_all_if_interactive at 0x7f4811a4ba00> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
276 def _draw_all_if_interactive() -> None:
277 if matplotlib.is_interactive():
--> 278 draw_all()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
129 for manager in cls.get_all_fig_managers():
130 if force or manager.canvas.figure.stale:
--> 131 manager.canvas.draw_idle()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
1891 if not self._is_idle_drawing:
1892 with self._idle_draw_cntx():
-> 1893 self.draw(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
379 # Acquire a lock on the shared font cache.
380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
381 else nullcontext()):
--> 382 self.figure.draw(self.renderer)
383 # A GUI class may be need to update a window using this draw, so
384 # don't forget to call the superclass.
385 super().draw()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4567, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4565 if ba:
4566 bb.append(ba)
-> 4567 self._update_title_position(renderer)
4568 axbbox = self.get_window_extent(renderer)
4569 bb.append(axbbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:3134, in _AxesBase._update_title_position(self, renderer)
3132 if title.get_text():
3133 for ax in axs:
-> 3134 ax.yaxis.get_tightbbox(renderer) # update offsetText
3135 if ax.yaxis.offsetText.get_text():
3136 bb = ax.yaxis.offsetText.get_tightbbox(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1332, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
-> 1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4567, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4565 if ba:
4566 bb.append(ba)
-> 4567 self._update_title_position(renderer)
4568 axbbox = self.get_window_extent(renderer)
4569 bb.append(axbbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:3134, in _AxesBase._update_title_position(self, renderer)
3132 if title.get_text():
3133 for ax in axs:
-> 3134 ax.yaxis.get_tightbbox(renderer) # update offsetText
3135 if ax.yaxis.offsetText.get_text():
3136 bb = ax.yaxis.offsetText.get_tightbbox(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1332, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
-> 1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
<Figure size 735x350 with 3 Axes>
Figure 8. (Left) mosaic.contour result for contours at \(-1, 0, 1\) (blue, orange, red) of the checkerboard field, evaluated on the MPAS mesh. (Right) plt.contour of the same checkerboard field on a regular quadrilateral grid for comparison.
As we can see above, we get good agreement between our mosaic.contour implementation and the plt.contour result. The major difference is the jagged appearance of the MPAS contours due to the coarse resolution of the mesh, but this is desired feature of implementation. plt.contour does linear interpolation, which produces the smooth boundaries, but that is not something we want (or at least on all the time). Future work will investigate adding optional support for smooth contours.
Filled Contours#
Filled contours, specifically the possibility of interior boundaries, presents a challenge to our treatment so far mesh contours as graphs. To illustrate this challenge we will visualize two, multi-component, isomorphic graphs, which according to graph theory are equivalent.
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4567, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4565 if ba:
4566 bb.append(ba)
-> 4567 self._update_title_position(renderer)
4568 axbbox = self.get_window_extent(renderer)
4569 bb.append(axbbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:3134, in _AxesBase._update_title_position(self, renderer)
3132 if title.get_text():
3133 for ax in axs:
-> 3134 ax.yaxis.get_tightbbox(renderer) # update offsetText
3135 if ax.yaxis.offsetText.get_text():
3136 bb = ax.yaxis.offsetText.get_tightbbox(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1332, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
-> 1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
<Figure size 735x350 with 3 Axes>
Figure 9. Two multi-componnet isomorphic graphs, plotted using their coordinate information.
As far a graph theory is concerned, the two graphs above are equivalent. Because we use the coordinate information to plot these graphs, we can see an important distinction: the graph on the left is nested where the graph on the right is separated.
For filled contours, Figure 9. is a crude, but illustrative, example of a contour with an interior boundary. So far we only used the mesh connectivity information to create the unfilled contour boundaries, where coordinate information has only been used to plotting. But, for filled contours after creating the contour boundaries from the connectivity information, we’ll need to use coordinate info to recursively search of interior boundaries within the resulting contours.
We can efficiently recursively search for interior boundaries using shapely.STRtree, which uses the bounding boxes of each geometry to query for containment.
Relative Ordering#
One final matplotlib consideration, is the relative ordering (ie., clockwise vs counter-clockwise) of interior boundaries relative to their exterior boundaries.
In order for an interior boundary to be unfilled, as intended, it’s relative order must be opposite the relative order of the exterior boundary.
For more information about this, refer to the matplotlib documentation: here.
Following the matplotlib documentation
Error in callback <function _draw_all_if_interactive at 0x7f4811a4ba00> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
276 def _draw_all_if_interactive() -> None:
277 if matplotlib.is_interactive():
--> 278 draw_all()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
129 for manager in cls.get_all_fig_managers():
130 if force or manager.canvas.figure.stale:
--> 131 manager.canvas.draw_idle()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
1891 if not self._is_idle_drawing:
1892 with self._idle_draw_cntx():
-> 1893 self.draw(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
379 # Acquire a lock on the shared font cache.
380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
381 else nullcontext()):
--> 382 self.figure.draw(self.renderer)
383 # A GUI class may be need to update a window using this draw, so
384 # don't forget to call the superclass.
385 super().draw()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4587, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4584 bbox_artists = self.get_default_bbox_extra_artists()
4586 for a in bbox_artists:
-> 4587 bbox = a.get_tightbbox(renderer)
4588 if (bbox is not None
4589 and 0 < bbox.width < np.inf
4590 and 0 < bbox.height < np.inf):
4591 bb.append(bbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:2032, in Annotation.get_tightbbox(self, renderer)
2030 if not self._check_xy(renderer):
2031 return Bbox.null()
-> 2032 return super().get_tightbbox(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:364, in Artist.get_tightbbox(self, renderer)
348 def get_tightbbox(self, renderer=None):
349 """
350 Like `.Artist.get_window_extent`, but includes any clipping.
351
(...) 362 Returns None if clipping results in no intersection.
363 """
--> 364 bbox = self.get_window_extent(renderer)
365 if self.get_clip_on():
366 clip_box = self.get_clip_box()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:2020, in Annotation.get_window_extent(self, renderer)
2016 raise RuntimeError('Cannot get window extent without renderer')
2018 self.update_positions(self._renderer)
-> 2020 text_bbox = Text.get_window_extent(self)
2021 bboxes = [text_bbox]
2023 if self.arrow_patch is not None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4587, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4584 bbox_artists = self.get_default_bbox_extra_artists()
4586 for a in bbox_artists:
-> 4587 bbox = a.get_tightbbox(renderer)
4588 if (bbox is not None
4589 and 0 < bbox.width < np.inf
4590 and 0 < bbox.height < np.inf):
4591 bb.append(bbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:2032, in Annotation.get_tightbbox(self, renderer)
2030 if not self._check_xy(renderer):
2031 return Bbox.null()
-> 2032 return super().get_tightbbox(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:364, in Artist.get_tightbbox(self, renderer)
348 def get_tightbbox(self, renderer=None):
349 """
350 Like `.Artist.get_window_extent`, but includes any clipping.
351
(...) 362 Returns None if clipping results in no intersection.
363 """
--> 364 bbox = self.get_window_extent(renderer)
365 if self.get_clip_on():
366 clip_box = self.get_clip_box()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:2020, in Annotation.get_window_extent(self, renderer)
2016 raise RuntimeError('Cannot get window extent without renderer')
2018 self.update_positions(self._renderer)
-> 2020 text_bbox = Text.get_window_extent(self)
2021 bboxes = [text_bbox]
2023 if self.arrow_patch is not None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
<Figure size 550x550 with 1 Axes>
Figure 10. Illustration of the importance of the interior vs. exterior relative ordering for rendering interior boundaries.
So, with above considerations in mind let’s return to the same sine field, evaluated on a \(100 \times 100\) cell mesh.
Error in callback <function _draw_all_if_interactive at 0x7f4811a4ba00> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
276 def _draw_all_if_interactive() -> None:
277 if matplotlib.is_interactive():
--> 278 draw_all()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
129 for manager in cls.get_all_fig_managers():
130 if force or manager.canvas.figure.stale:
--> 131 manager.canvas.draw_idle()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
1891 if not self._is_idle_drawing:
1892 with self._idle_draw_cntx():
-> 1893 self.draw(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
379 # Acquire a lock on the shared font cache.
380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
381 else nullcontext()):
--> 382 self.figure.draw(self.renderer)
383 # A GUI class may be need to update a window using this draw, so
384 # don't forget to call the superclass.
385 super().draw()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:405, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
403 cbp_rspan, cbp_cspan = get_cb_parent_spans(cbax)
404 loc = cbax._colorbar_info['location']
--> 405 cbpos, cbbbox = get_pos_and_bbox(cbax, renderer)
406 if loc == 'right':
407 if cbp_cspan.stop == ss.colspan.stop:
408 # only increase if the colorbar is on the right edge
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4564, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4562 for axis in self._axis_map.values():
4563 if self.axison and axis.get_visible():
-> 4564 ba = martist._get_tightbbox_for_layout_only(axis, renderer)
4565 if ba:
4566 bb.append(ba)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1334, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
-> 1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:405, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
403 cbp_rspan, cbp_cspan = get_cb_parent_spans(cbax)
404 loc = cbax._colorbar_info['location']
--> 405 cbpos, cbbbox = get_pos_and_bbox(cbax, renderer)
406 if loc == 'right':
407 if cbp_cspan.stop == ss.colspan.stop:
408 # only increase if the colorbar is on the right edge
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4564, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4562 for axis in self._axis_map.values():
4563 if self.axison and axis.get_visible():
-> 4564 ba = martist._get_tightbbox_for_layout_only(axis, renderer)
4565 if ba:
4566 bb.append(ba)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1334, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
-> 1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
<Figure size 350x350 with 2 Axes>
Figure 11. Checkerboard field, evaluated on a \(100 \times 100\) cell mesh.
Like filled contouring, our first step will be to create the boolean mask. Because this is a filled contour we will need both an upper and lower bound (cf., unfilled).
cell_mask = (field > -1.0) & (field < 0.0)
Figure 12. (Left) Boolean mask of \(-1.0 < f(x, y) < 0.0\) evaluated on the MPAS mesh. (Right) The same checkerboard field, evaluated and contoured on a regular quadrilateral mesh. The grey is the filled contour area.
As a first step, we will use the exact same steps as unfilled contours to extract and sort the contour boundaries using the connectivity information. One important difference between the filled (grey) and unfilled (blue lines) is that the filled contours follow the mesh boundary.
Figure 13. The contour boundaries identified just using connectivity information. Each unique contour component has it’s own color. Notice the pink and brown boundaries are really interior boundaries of the blue contour, but just using connectivity information alone is not enough to identify this.
We will now used the coordinate information to recursively search for containment of our contour components.
If we find an interior boundary during this recursive search, we will also ensure it’s relative order is opposite it’s parent to ensure proper plotting in matplotlib once we will the contours.
Figure 14. The contour boundaries identified after recursively search for containment using the coordinate information. Notice the interior boundaries of the blue contour are now blue as we’d expect.
With all this in hand, we now have a fully functional filled contour algorthinm.
Now let’s see it in practice and compare to our matplotlib regularly spaced reference.
Error in callback <function _draw_all_if_interactive at 0x7f4811a4ba00> (for post_execute), with arguments args (),kwargs {}:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/pyplot.py:278, in _draw_all_if_interactive()
276 def _draw_all_if_interactive() -> None:
277 if matplotlib.is_interactive():
--> 278 draw_all()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_pylab_helpers.py:131, in Gcf.draw_all(cls, force)
129 for manager in cls.get_all_fig_managers():
130 if force or manager.canvas.figure.stale:
--> 131 manager.canvas.draw_idle()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:1893, in FigureCanvasBase.draw_idle(self, *args, **kwargs)
1891 if not self._is_idle_drawing:
1892 with self._idle_draw_cntx():
-> 1893 self.draw(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:382, in FigureCanvasAgg.draw(self)
379 # Acquire a lock on the shared font cache.
380 with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
381 else nullcontext()):
--> 382 self.figure.draw(self.renderer)
383 # A GUI class may be need to update a window using this draw, so
384 # don't forget to call the superclass.
385 super().draw()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4567, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4565 if ba:
4566 bb.append(ba)
-> 4567 self._update_title_position(renderer)
4568 axbbox = self.get_window_extent(renderer)
4569 bb.append(axbbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:3134, in _AxesBase._update_title_position(self, renderer)
3132 if title.get_text():
3133 for ax in axs:
-> 3134 ax.yaxis.get_tightbbox(renderer) # update offsetText
3135 if ax.yaxis.offsetText.get_text():
3136 bb = ax.yaxis.offsetText.get_tightbbox(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1332, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
-> 1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:187, in retina_figure(fig, base64, **kwargs)
178 def retina_figure(fig, base64=False, **kwargs):
179 """format a figure as a pixel-doubled (retina) PNG
180
181 If `base64` is True, return base64-encoded str instead of raw bytes
(...) 185 base64 argument
186 """
--> 187 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
188 # Make sure that retina_figure acts just like print_figure and returns
189 # None when the figure is empty.
190 if pngdata is None:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/IPython/core/pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:2157, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2154 # we do this instead of `self.figure.draw_without_rendering`
2155 # so that we can inject the orientation
2156 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2157 self.figure.draw(renderer)
2158 if bbox_inches:
2159 if bbox_inches == "tight":
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/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 ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/figure.py:3251, in Figure.draw(self, renderer)
3249 if self.axes and self.get_layout_engine() is not None:
3250 try:
-> 3251 self.get_layout_engine().execute(self)
3252 except ValueError:
3253 pass
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/layout_engine.py:278, in ConstrainedLayoutEngine.execute(self, fig)
275 w_pad = self._params['w_pad'] / width
276 h_pad = self._params['h_pad'] / height
--> 278 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
279 wspace=self._params['wspace'],
280 hspace=self._params['hspace'],
281 rect=self._params['rect'],
282 compress=self._compress)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:116, in do_constrained_layout(fig, h_pad, w_pad, hspace, wspace, rect, compress)
106 return
108 for _ in range(2):
109 # do the algorithm twice. This has to be done because decorations
110 # change size after the first re-position (i.e. x/yticklabels get
(...) 114 # make margins for all the Axes and subfigures in the
115 # figure. Add margins for colorbars...
--> 116 make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
117 w_pad=w_pad, hspace=hspace, wspace=wspace)
118 make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
119 w_pad=w_pad)
121 # if a layout is such that a columns (or rows) margin has no
122 # constraints, we need to make all such instances in the grid
123 # match in margin size.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:388, in make_layout_margins(layoutgrids, fig, renderer, w_pad, h_pad, hspace, wspace)
384 return
386 margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
387 hspace=hspace, wspace=wspace)
--> 388 pos, bbox = get_pos_and_bbox(ax, renderer)
389 # the margin is the distance between the bounding box of the Axes
390 # and its position (plus the padding from above)
391 margin['left'] += pos.x0 - bbox.x0
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/_constrained_layout.py:645, in get_pos_and_bbox(ax, renderer)
643 # pos is in panel co-ords, but we need in figure for the layout
644 pos = pos.transformed(fig.transSubfigure - fig.transFigure)
--> 645 tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
646 if tightbbox is None:
647 bbox = pos
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/artist.py:1402, in _get_tightbbox_for_layout_only(obj, *args, **kwargs)
1396 """
1397 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1398 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1399 when encountering third-party subclasses that do not support it.
1400 """
1401 try:
-> 1402 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1403 except TypeError:
1404 return obj.get_tightbbox(*args, **kwargs)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:4567, in _AxesBase.get_tightbbox(self, renderer, call_axes_locator, bbox_extra_artists, for_layout_only)
4565 if ba:
4566 bb.append(ba)
-> 4567 self._update_title_position(renderer)
4568 axbbox = self.get_window_extent(renderer)
4569 bb.append(axbbox)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axes/_base.py:3134, in _AxesBase._update_title_position(self, renderer)
3132 if title.get_text():
3133 for ax in axs:
-> 3134 ax.yaxis.get_tightbbox(renderer) # update offsetText
3135 if ax.yaxis.offsetText.get_text():
3136 bb = ax.yaxis.offsetText.get_tightbbox(renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1353, in Axis.get_tightbbox(self, renderer, for_layout_only)
1350 renderer = self.get_figure(root=True)._get_renderer()
1351 ticks_to_draw = self._update_ticks()
-> 1353 self._update_label_position(renderer)
1355 # go back to just this axis's tick labels
1356 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2675, in YAxis._update_label_position(self, renderer)
2671 return
2673 # get bounding boxes for this axis and any siblings
2674 # that have been set by `fig.align_ylabels()`
-> 2675 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2676 x, y = self.label.get_position()
2678 if self.label_position == 'left':
2679 # Union with extents of the left spine if present, of the axes otherwise.
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:2241, in Axis._get_tick_boxes_siblings(self, renderer)
2239 axis = ax._axis_map[name]
2240 ticks_to_draw = axis._update_ticks()
-> 2241 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2242 bboxes.extend(tlb)
2243 bboxes2.extend(tlb2)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/axis.py:1332, in Axis._get_ticklabel_bboxes(self, ticks, renderer)
1330 if renderer is None:
1331 renderer = self.get_figure(root=True)._get_renderer()
-> 1332 return ([tick.label1.get_window_extent(renderer)
1333 for tick in ticks if tick.label1.get_visible()],
1334 [tick.label2.get_window_extent(renderer)
1335 for tick in ticks if tick.label2.get_visible()])
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:969, in Text.get_window_extent(self, renderer, dpi)
964 raise RuntimeError(
965 "Cannot get window extent of text w/o renderer. You likely "
966 "want to call 'figure.draw_without_rendering()' first.")
968 with cbook._setattr_cm(fig, dpi=dpi):
--> 969 bbox, info, descent = self._get_layout(self._renderer)
970 x, y = self.get_unitless_position()
971 x, y = self.get_transform().transform((x, y))
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:373, in Text._get_layout(self, renderer)
370 ys = []
372 # Full vertical extent of font, including ascenders and descenders:
--> 373 _, lp_h, lp_d = _get_text_metrics_with_cache(
374 renderer, "lp", self._fontproperties,
375 ismath="TeX" if self.get_usetex() else False,
376 dpi=self.get_figure(root=True).dpi)
377 min_dy = (lp_h - lp_d) * self._linespacing
379 for i, line in enumerate(lines):
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:69, in _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi)
66 """Call ``renderer.get_text_width_height_descent``, caching the results."""
67 # Cached based on a copy of fontprop so that later in-place mutations of
68 # the passed-in argument do not mess up the cache.
---> 69 return _get_text_metrics_with_cache_impl(
70 weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/text.py:77, in _get_text_metrics_with_cache_impl(renderer_ref, text, fontprop, ismath, dpi)
73 @functools.lru_cache(4096)
74 def _get_text_metrics_with_cache_impl(
75 renderer_ref, text, fontprop, ismath, dpi):
76 # dpi is unused, but participates in cache invalidation (via the renderer).
---> 77 return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py:211, in RendererAgg.get_text_width_height_descent(self, s, prop, ismath)
209 _api.check_in_list(["TeX", True, False], ismath=ismath)
210 if ismath == "TeX":
--> 211 return super().get_text_width_height_descent(s, prop, ismath)
213 if ismath:
214 ox, oy, width, height, descent, font_image = \
215 self.mathtext_parser.parse(s, self.dpi, prop)
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/backend_bases.py:566, in RendererBase.get_text_width_height_descent(self, s, prop, ismath)
562 fontsize = prop.get_size_in_points()
564 if ismath == 'TeX':
565 # todo: handle properties
--> 566 return self.get_texmanager().get_text_width_height_descent(
567 s, fontsize, renderer=self)
569 dpi = self.points_to_pixels(72)
570 if ismath:
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/texmanager.py:367, in TexManager.get_text_width_height_descent(cls, tex, fontsize, renderer)
365 dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1
366 with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi:
--> 367 page, = dvi
368 # A total height (including the descent) needs to be returned.
369 return page.width, page.height + page.descent, page.descent
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:261, in Dvi.__iter__(self)
245 def __iter__(self):
246 """
247 Iterate through the pages of the file.
248
(...) 259 integers.
260 """
--> 261 while self._read():
262 yield self._output()
File ~/miniforge3/envs/mosaic-dev/lib/python3.14/site-packages/matplotlib/dviread.py:343, in Dvi._read(self)
341 self._dtable[byte](self, byte)
342 if self._missing_font:
--> 343 raise self._missing_font.to_exception()
344 name = self._dtable[byte].__name__
345 if name == "_push":
FileNotFoundError: Matplotlib's TeX implementation searched for a file named 'cmss10.tfm' in your texmf tree, but could not find it
<Figure size 735x350 with 3 Axes>
Figure 15. mosaic.contourf result for filled contour levels of \(-1, 0, 1\) of the checkerboard field, evaluated on the MPAS mesh. (Right) plt.contourf of the same checkerboard field on a regular quadrilateral grid for comparison.
As we can see above, we also get good agreement between our mosaic.contourf implementation and the plt.contourf result.
Again, the major difference is the jagged appearance of the MPAS contours due to the coarse resolution of the mesh, but this is desired feature of implementation.
Future Work#
Smooth (interpolated) contours:#
https://www.boristhebrave.com/2018/04/15/marching-cubes-tutorial/
https://www.cs.wustl.edu/~taoju/cse554/lectures/lect04_Contouring_I.pdf
https://www.cse.wustl.edu/~taoju/research/dualContour.pdf
https://en.wikipedia.org/wiki/Marching_squares
Marching Squares implementation:#
skimagehas acythonbased marching squares implementation that we could probably easily port to marching triangles.https://github.com/scikit-image/scikit-image/blob/main/src/skimage/measure/_find_contours_cy.pyx