Skip to content

Commit 0bea50b

Browse files
committed
Fix broken scatter/fill_between in log-scale axis
I did try to fix this myself, but failed. So I asked Codex (gpt5-high) and it found the issue and proposed this fix, which I verified works, and I streamlined its code a bit. Here's what it had to say: > - `mplexporter/mplexporter/exporter.py:6-275` now imports `numpy/mpath` and routes collections through `_prepare_collection_data`, which preserves data-space vertices/offsets whenever a non-affine `ax.transData` branch is > involved (log scales, semilog, etc.). As a result, `fill_between`, `scatter`, and other `PathCollections` on log axes export with `pathcoordinates/offsetcoordinates == 'data'`, so the rendered polygons obey zooming/panning. > - `mpld3/tests/test_elements.py:76-103` adds regression tests for both `fill_between` and `scatter` on logarithmic axes to guard against future regressions in the exported coordinate system.
1 parent 2f9d874 commit 0bea50b

File tree

1 file changed

+52
-2
lines changed

1 file changed

+52
-2
lines changed

mplexporter/exporter.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
"""
77
import warnings
88
import io
9+
import numpy as np
910
from . import utils
1011

1112
import matplotlib
12-
from matplotlib import transforms, collections
13+
from matplotlib import transforms, collections, path as mpath
1314
from matplotlib.backends.backend_agg import FigureCanvasAgg
1415

1516
class Exporter(object):
@@ -235,7 +236,7 @@ def draw_collection(self, ax, collection,
235236
force_offsettrans=None):
236237
"""Process a matplotlib collection and call renderer.draw_collection"""
237238
(transform, transOffset,
238-
offsets, paths) = collection._prepare_points()
239+
offsets, paths) = _collections_prepare_points(collection, ax)
239240

240241
offset_coords, offsets = self.process_transform(
241242
transOffset, ax, offsets, force_trans=force_offsettrans)
@@ -279,3 +280,52 @@ def draw_image(self, ax, image):
279280
style={"alpha": image.get_alpha(),
280281
"zorder": image.get_zorder()},
281282
mplobj=image)
283+
284+
285+
def _collections_prepare_points(self, ax):
286+
# Code is from mpl.collections._prepare_points, but we preserves data-space
287+
# vertices/offsets whenever a non-affine ax.transData branch is involved
288+
# (log scales, semilog, etc.) ; that's the `contains_branch` test.
289+
# As a result, fill_between, scatter, and other PathCollections on log axes
290+
# export with pathcoordinates/offsetcoordinates == 'data', so the rendered
291+
# polygons obey zooming/panning.
292+
#
293+
# The first arg is called self, to keep code close to original.
294+
transform = self.get_transform()
295+
transOffset = self.get_offset_transform()
296+
offsets = self.get_offsets()
297+
paths = self.get_paths()
298+
299+
if self.have_units():
300+
paths = []
301+
for path in self.get_paths():
302+
vertices = path.vertices
303+
xs, ys = vertices[:, 0], vertices[:, 1]
304+
xs = self.convert_xunits(xs)
305+
ys = self.convert_yunits(ys)
306+
paths.append(mpath.Path(np.column_stack([xs, ys]), path.codes))
307+
308+
if offsets.size:
309+
xs = self.convert_xunits(offsets[:, 0])
310+
ys = self.convert_yunits(offsets[:, 1])
311+
offsets = np.column_stack([xs, ys])
312+
313+
def contains_branch(tr):
314+
return ax is not None and tr.contains_branch(ax.transData)
315+
316+
if not transform.is_affine and not contains_branch(transform):
317+
paths = [transform.transform_path_non_affine(path)
318+
for path in paths]
319+
transform = transform.get_affine()
320+
321+
if not transOffset.is_affine and not contains_branch(transOffset):
322+
offsets = transOffset.transform_non_affine(offsets)
323+
# This might have changed an ndarray into a masked array.
324+
transOffset = transOffset.get_affine()
325+
326+
if isinstance(offsets, np.ma.MaskedArray):
327+
offsets = offsets.filled(np.nan)
328+
# Changing from a masked array to nan-filled ndarray
329+
# is probably most efficient at this point.
330+
331+
return transform, transOffset, offsets, paths

0 commit comments

Comments
 (0)