From f70df4c7103069099a307053586252b755de213b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 2 Oct 2020 21:43:26 -0400 Subject: [PATCH 1/5] Return minpos from _path.get_path_collection_extents. This is already calculated by the internal C++ code, but discarded at the end of the Python function. --- lib/matplotlib/path.py | 5 +++-- src/_path_wrapper.cpp | 10 ++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index a9a220479759..b0e984e17abc 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -1039,6 +1039,7 @@ def get_path_collection_extents( from .transforms import Bbox if len(paths) == 0: raise ValueError("No paths provided") - return Bbox.from_extents(*_path.get_path_collection_extents( + extents, minpos = _path.get_path_collection_extents( master_transform, paths, np.atleast_3d(transforms), - offsets, offset_transform)) + offsets, offset_transform) + return Bbox.from_extents(*extents) diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 708d7d36e67a..e28043363047 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -250,7 +250,8 @@ static PyObject *Py_update_path_extents(PyObject *self, PyObject *args, PyObject "NNi", outextents.pyobj(), outminpos.pyobj(), changed); } -const char *Py_get_path_collection_extents__doc__ = "get_path_collection_extents("; +const char *Py_get_path_collection_extents__doc__ = "get_path_collection_extents(" + "master_transform, paths, transforms, offsets, offset_transform)"; static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args, PyObject *kwds) { @@ -295,7 +296,12 @@ static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args, extents(1, 0) = e.x1; extents(1, 1) = e.y1; - return extents.pyobj(); + npy_intp minposdims[] = { 2 }; + numpy::array_view minpos(minposdims); + minpos(0) = e.xm; + minpos(1) = e.ym; + + return Py_BuildValue("NN", extents.pyobj(), minpos.pyobj()); } const char *Py_point_in_path_collection__doc__ = From 7e69d180d7ee1c01c845db9c0233d92b5bf7ae96 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 2 Oct 2020 22:13:32 -0400 Subject: [PATCH 2/5] Propagate minpos from Collections to Axes.dataLim. This ensures that autoscaling on log scales is correct. --- lib/matplotlib/axes/_base.py | 8 +++++++- lib/matplotlib/collections.py | 12 ++++++------ lib/matplotlib/path.py | 2 +- lib/matplotlib/transforms.py | 10 +++++++--- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 26bc28dea71a..aa3d74757ce6 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2000,7 +2000,13 @@ def add_collection(self, collection, autolim=True): # Make sure viewLim is not stale (mostly to match # pre-lazy-autoscale behavior, which is not really better). self._unstale_viewLim() - self.update_datalim(collection.get_datalim(self.transData)) + datalim = collection.get_datalim(self.transData) + # By definition, p0 <= minpos <= p1, so minpos would be + # unnecessary. However, we add minpos to the call so that + # self.dataLim will update its own minpos. This ensures that log + # scales see the correct minimum. + self.update_datalim( + np.row_stack([datalim.p0, datalim.minpos, datalim.p1])) self.stale = True return collection diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 51c6c50a0305..c6ae27390d0e 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -274,11 +274,11 @@ def get_datalim(self, transData): # can properly have the axes limits set by their shape + # offset. LineCollections that have no offsets can # also use this algorithm (like streamplot). - result = mpath.get_path_collection_extents( - transform.get_affine(), paths, self.get_transforms(), + return mpath.get_path_collection_extents( + transform.get_affine() - transData, paths, + self.get_transforms(), transOffset.transform_non_affine(offsets), transOffset.get_affine().frozen()) - return result.transformed(transData.inverted()) if not self._offsetsNone: # this is for collections that have their paths (shapes) # in physical, axes-relative, or figure-relative units @@ -290,9 +290,9 @@ def get_datalim(self, transData): # note A-B means A B^{-1} offsets = np.ma.masked_invalid(offsets) if not offsets.mask.all(): - points = np.row_stack((offsets.min(axis=0), - offsets.max(axis=0))) - return transforms.Bbox(points) + bbox = transforms.Bbox.null() + bbox.update_from_data_xy(offsets) + return bbox return transforms.Bbox.null() def get_window_extent(self, renderer): diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index b0e984e17abc..c37e3026cc53 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -1042,4 +1042,4 @@ def get_path_collection_extents( extents, minpos = _path.get_path_collection_extents( master_transform, paths, np.atleast_3d(transforms), offsets, offset_transform) - return Bbox.from_extents(*extents) + return Bbox.from_extents(*extents, minpos=minpos) diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index ba29e6719740..651ed09876c5 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -808,13 +808,17 @@ def from_bounds(x0, y0, width, height): return Bbox.from_extents(x0, y0, x0 + width, y0 + height) @staticmethod - def from_extents(*args): + def from_extents(*args, minpos=None): """ Create a new Bbox from *left*, *bottom*, *right* and *top*. - The *y*-axis increases upwards. + The *y*-axis increases upwards. Optionally, passing *minpos* will set + that property on the returned Bbox. """ - return Bbox(np.reshape(args, (2, 2))) + bbox = Bbox(np.reshape(args, (2, 2))) + if minpos is not None: + bbox._minpos[:] = minpos + return bbox def __format__(self, fmt): return ( From 279ec452a0f0b18a464a3b0280074cadf2cb2192 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 2 Oct 2020 22:28:58 -0400 Subject: [PATCH 3/5] Add a test for scatter autolim on log scale. This test is a distilled out of #16552. --- lib/matplotlib/tests/test_collections.py | 28 +++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index f125f771f913..1f5deddda1ff 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -11,7 +11,7 @@ import matplotlib.transforms as mtransforms from matplotlib.collections import (Collection, LineCollection, EventCollection, PolyCollection) -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import check_figures_equal, image_comparison def generate_EventCollection_plot(): @@ -301,6 +301,32 @@ def test_add_collection(): assert ax.dataLim.bounds == bounds +@pytest.mark.style('mpl20') +@check_figures_equal(extensions=['png']) +def test_collection_log_datalim(fig_test, fig_ref): + # Data limits should respect the minimum x/y when using log scale. + x_vals = [4.38462e-6, 5.54929e-6, 7.02332e-6, 8.88889e-6, 1.12500e-5, + 1.42383e-5, 1.80203e-5, 2.28070e-5, 2.88651e-5, 3.65324e-5, + 4.62363e-5, 5.85178e-5, 7.40616e-5, 9.37342e-5, 1.18632e-4] + y_vals = [0.0, 0.1, 0.182, 0.332, 0.604, 1.1, 2.0, 3.64, 6.64, 12.1, 22.0, + 39.6, 71.3] + + x, y = np.meshgrid(x_vals, y_vals) + x = x.flatten() + y = y.flatten() + + ax_test = fig_test.subplots() + ax_test.set_xscale('log') + ax_test.set_yscale('log') + ax_test.margins = 0 + ax_test.scatter(x, y) + + ax_ref = fig_ref.subplots() + ax_ref.set_xscale('log') + ax_ref.set_yscale('log') + ax_ref.plot(x, y, marker="o", ls="") + + def test_quiver_limits(): ax = plt.axes() x, y = np.arange(8), np.arange(10) From 06786f792a0d756c474e357883e5d4b759ad62fb Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 2 Oct 2020 22:48:44 -0400 Subject: [PATCH 4/5] Only propagate minpos if it's been set. This is mostly for the sake of third-party `Collection` subclasses that might have overridden `get_datalim`. --- lib/matplotlib/axes/_base.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index aa3d74757ce6..39d7484c74d6 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2001,12 +2001,15 @@ def add_collection(self, collection, autolim=True): # pre-lazy-autoscale behavior, which is not really better). self._unstale_viewLim() datalim = collection.get_datalim(self.transData) - # By definition, p0 <= minpos <= p1, so minpos would be - # unnecessary. However, we add minpos to the call so that - # self.dataLim will update its own minpos. This ensures that log - # scales see the correct minimum. - self.update_datalim( - np.row_stack([datalim.p0, datalim.minpos, datalim.p1])) + points = datalim.get_points() + if not np.isinf(datalim.minpos).all(): + # By definition, if minpos (minimum positive value) is set + # (i.e., non-inf), then min(points) <= minpos <= max(points), + # and minpos would be superfluous. However, we add minpos to + # the call so that self.dataLim will update its own minpos. + # This ensures that log scales see the correct minimum. + points = np.concatenate([points, [datalim.minpos]]) + self.update_datalim(points) self.stale = True return collection From e85ea1bbc6d75f51e2aa6cb1fee76c7b0977792b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 16 Oct 2020 02:37:04 -0400 Subject: [PATCH 5/5] Add documentation for Bbox.minpos*. --- lib/matplotlib/transforms.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index 651ed09876c5..b96c03a018e2 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -812,8 +812,17 @@ def from_extents(*args, minpos=None): """ Create a new Bbox from *left*, *bottom*, *right* and *top*. - The *y*-axis increases upwards. Optionally, passing *minpos* will set - that property on the returned Bbox. + The *y*-axis increases upwards. + + Parameters + ---------- + left, bottom, right, top : float + The four extents of the bounding box. + + minpos : float or None + If this is supplied, the Bbox will have a minimum positive value + set. This is useful when dealing with logarithmic scales and other + scales where negative bounds result in floating point errors. """ bbox = Bbox(np.reshape(args, (2, 2))) if minpos is not None: @@ -957,14 +966,35 @@ def bounds(self, bounds): @property def minpos(self): + """ + The minimum positive value in both directions within the Bbox. + + This is useful when dealing with logarithmic scales and other scales + where negative bounds result in floating point errors, and will be used + as the minimum extent instead of *p0*. + """ return self._minpos @property def minposx(self): + """ + The minimum positive value in the *x*-direction within the Bbox. + + This is useful when dealing with logarithmic scales and other scales + where negative bounds result in floating point errors, and will be used + as the minimum *x*-extent instead of *x0*. + """ return self._minpos[0] @property def minposy(self): + """ + The minimum positive value in the *y*-direction within the Bbox. + + This is useful when dealing with logarithmic scales and other scales + where negative bounds result in floating point errors, and will be used + as the minimum *y*-extent instead of *y0*. + """ return self._minpos[1] def get_points(self): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy