diff --git a/doc/api/next_api_changes/behavior/19214-DS.rst b/doc/api/next_api_changes/behavior/19214-DS.rst new file mode 100644 index 000000000000..cffd0b8ffd6f --- /dev/null +++ b/doc/api/next_api_changes/behavior/19214-DS.rst @@ -0,0 +1,5 @@ +Improved autoscaling for bezier curves +-------------------------------------- +Bezier curves are now autoscaled to their extents - previously they were +autoscaled to their ends and control points, which in some cases led to +unnecessarily large limits. diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 37a3d39ed77f..007c45045835 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -22,7 +22,6 @@ import matplotlib.image as mimage import matplotlib.lines as mlines import matplotlib.patches as mpatches -import matplotlib.path as mpath from matplotlib.rcsetup import cycler, validate_axisbelow import matplotlib.spines as mspines import matplotlib.table as mtable @@ -2375,10 +2374,18 @@ def _update_patch_limits(self, patch): ((not patch.get_width()) and (not patch.get_height()))): return p = patch.get_path() - vertices = p.vertices if p.codes is None else p.vertices[np.isin( - p.codes, (mpath.Path.CLOSEPOLY, mpath.Path.STOP), invert=True)] - if not vertices.size: - return + # Get all vertices on the path + # Loop through each sement to get extrema for Bezier curve sections + vertices = [] + for curve, code in p.iter_bezier(): + # Get distance along the curve of any extrema + _, dzeros = curve.axis_aligned_extrema() + # Calculate vertcies of start, end and any extrema in between + vertices.append(curve([0, *dzeros, 1])) + + if len(vertices): + vertices = np.row_stack(vertices) + patch_trf = patch.get_transform() updatex, updatey = patch_trf.contains_branch_seperately(self.transData) if not (updatex or updatey): diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_ccw_true.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_ccw_true.png index 8af5f62b5526..c5236a34b9e1 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_ccw_true.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_ccw_true.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_center_radius.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_center_radius.png index f7b7108ce79b..64b2244711f9 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_center_radius.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_center_radius.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png index 1c0c6c2c0577..f3935a9e159a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_default.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_frame_grid.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_frame_grid.png index 4eea9fb7c157..4e4edbeed0ed 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_frame_grid.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_frame_grid.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_0.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_0.png index 18acc069100e..e814e061205a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_0.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_0.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_2.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_2.png index 68fc84a4d596..e12d743fbc45 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_2.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_linewidth_2.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_no_label.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_no_label.png index d2d651e421dc..c6fd5262acce 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_no_label.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_no_label.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/pie_rotatelabels_true.png b/lib/matplotlib/tests/baseline_images/test_axes/pie_rotatelabels_true.png index 91664207dee7..d5875752c3cd 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/pie_rotatelabels_true.png and b/lib/matplotlib/tests/baseline_images/test_axes/pie_rotatelabels_true.png differ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 5da2df1455db..d971debc1486 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -24,6 +24,7 @@ import matplotlib.font_manager as mfont_manager import matplotlib.markers as mmarkers import matplotlib.patches as mpatches +import matplotlib.path as mpath import matplotlib.pyplot as plt import matplotlib.ticker as mticker import matplotlib.transforms as mtransforms @@ -5042,7 +5043,7 @@ def test_pie_default(): @image_comparison(['pie_linewidth_0', 'pie_linewidth_0', 'pie_linewidth_0'], - extensions=['png']) + extensions=['png'], style='mpl20') def test_pie_linewidth_0(): # The slices will be ordered and plotted counter-clockwise. labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' @@ -5074,7 +5075,7 @@ def test_pie_linewidth_0(): plt.axis('equal') -@image_comparison(['pie_center_radius.png']) +@image_comparison(['pie_center_radius.png'], style='mpl20') def test_pie_center_radius(): # The slices will be ordered and plotted counter-clockwise. labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' @@ -5094,7 +5095,7 @@ def test_pie_center_radius(): plt.axis('equal') -@image_comparison(['pie_linewidth_2.png']) +@image_comparison(['pie_linewidth_2.png'], style='mpl20') def test_pie_linewidth_2(): # The slices will be ordered and plotted counter-clockwise. labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' @@ -5109,7 +5110,7 @@ def test_pie_linewidth_2(): plt.axis('equal') -@image_comparison(['pie_ccw_true.png']) +@image_comparison(['pie_ccw_true.png'], style='mpl20') def test_pie_ccw_true(): # The slices will be ordered and plotted counter-clockwise. labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' @@ -5124,7 +5125,7 @@ def test_pie_ccw_true(): plt.axis('equal') -@image_comparison(['pie_frame_grid.png']) +@image_comparison(['pie_frame_grid.png'], style='mpl20') def test_pie_frame_grid(): # The slices will be ordered and plotted counter-clockwise. labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' @@ -5151,7 +5152,7 @@ def test_pie_frame_grid(): plt.axis('equal') -@image_comparison(['pie_rotatelabels_true.png']) +@image_comparison(['pie_rotatelabels_true.png'], style='mpl20') def test_pie_rotatelabels_true(): # The slices will be ordered and plotted counter-clockwise. labels = 'Hogwarts', 'Frogs', 'Dogs', 'Logs' @@ -7340,3 +7341,24 @@ def test_clim(): clim = (7, 8) norm = plot_method(clim=clim).norm assert (norm.vmin, norm.vmax) == clim + + +def test_bezier_autoscale(): + # Check that bezier curves autoscale to their curves, and not their + # control points + verts = [[-1, 0], + [0, -1], + [1, 0], + [1, 0]] + codes = [mpath.Path.MOVETO, + mpath.Path.CURVE3, + mpath.Path.CURVE3, + mpath.Path.CLOSEPOLY] + p = mpath.Path(verts, codes) + + fig, ax = plt.subplots() + ax.add_patch(mpatches.PathPatch(p)) + ax.autoscale() + # Bottom ylim should be at the edge of the curve (-0.5), and not include + # the control point (at -1) + assert ax.get_ylim()[0] == -0.5