Skip to content

Improve autoscaling for high order Bezier curves #19214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/api/next_api_changes/behavior/19214-DS.rst
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 12 additions & 5 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Comment on lines +2386 to +2387
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you not want to return here if empty any more?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps? It's a private helper function and doesn't seem to break anything though - I guess we're always passing in input that has some vertices.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's because transform and update_datalim are safe to empty lists. I think this is more of an optimization to avoid decomposing the transform, etc.


patch_trf = patch.get_transform()
updatex, updatey = patch_trf.contains_branch_seperately(self.transData)
if not (updatex or updatey):
Expand Down
Binary file modified lib/matplotlib/tests/baseline_images/test_axes/pie_ccw_true.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified lib/matplotlib/tests/baseline_images/test_axes/pie_default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified lib/matplotlib/tests/baseline_images/test_axes/pie_no_label.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 28 additions & 6 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -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