Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Addresses issue #24618 "Road sign" boxstyle/annotation, alternative to #24697 #24744

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

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
92 changes: 84 additions & 8 deletions lib/matplotlib/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -2372,14 +2372,35 @@ def __call__(self, x0, y0, width, height, mutation_size):
class LArrow:
"""A box in the shape of a left-pointing arrow."""

def __init__(self, pad=0.3):
def __init__(self, pad=0.3, head_width=1.5, head_angle=90.0):
"""
Parameters
----------
pad : float, default: 0.3
The amount of padding around the original box.
head_width : float, default: 1.5
The width of the arrow head versus the body. The minimum value
is 0.0 and the maximum value is 10.0. Any value smaller or
greater than this is contrained to the edge values.
head_angle : float, default: 90.0
The inside angle of the tip of the arrow. The minimum value is
10.0 and the maximum value is 179.0. Any value smaller or
greater than this is contrained to the edge values.
"""
self.pad = pad
if head_width > 10:
Copy link
Member

Choose a reason for hiding this comment

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

This can be done as self.head_width = np.clip(head_width, 0, 10)

self.head_width = 10
elif head_width < 0:
self.head_width = 0
else:
self.head_width = head_width

if head_angle >= 180:
self.head_angle = 179
elif head_angle < 10:
self.head_angle = 10
else:
self.head_angle = head_angle

def __call__(self, x0, y0, width, height, mutation_size):
# padding
Expand All @@ -2394,10 +2415,26 @@ def __call__(self, x0, y0, width, height, mutation_size):
dxx = dx / 2
x0 = x0 + pad / 1.4 # adjust by ~sqrt(2)

# The width adjustment is the value that must be added or
# subtracted from y_0 and y_1 for the ends of the head.
# The body width is 2dx.
# Subtracting 1 from the head_width gives what percentage of the
# body width is in the head, and there is .5x of that on each side.
# The .5 cancels out the 2dx for the body width.
width_adjustment = (self.head_width - 1) * dx

# The angle adjustment is the value that must be subtracted/added
# from x_0 or x_1 for the position of the tip.
# each half of the arrow head is a right angle triangle. Therefore,
# each half of the arrow head has the equation tan(head_angle/2)=
# (dx+width_adjustment)/(dxx+angle_adjustment).
angle_adjustment = ((dx + width_adjustment) / math.tan((self.
head_angle/2) * (math.pi/180))) - dxx

return Path._create_closed(
[(x0 + dxx, y0), (x1, y0), (x1, y1), (x0 + dxx, y1),
(x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx),
(x0 + dxx, y0 - dxx), # arrow
(x0 + dxx, y1 + width_adjustment), (x0 - angle_adjustment, y0
+ dx), (x0 + dxx, y0 - width_adjustment), # arrow
(x0 + dxx, y0)])

@_register_style(_style_list)
Expand All @@ -2415,14 +2452,35 @@ class DArrow:
"""A box in the shape of a two-way arrow."""
# Modified from LArrow to add a right arrow to the bbox.

def __init__(self, pad=0.3):
def __init__(self, pad=0.3, head_width=1.5, head_angle=90.0):
"""
Parameters
----------
pad : float, default: 0.3
The amount of padding around the original box.
head_width : float, default: 1.5
The width of the arrow head versus the body. The minimum value
is 0.0 and the maximum value is 10.0. Any value smaller or
greater than this is contrained to the edge values.
head_angle : float, default: 90.0
The inside angle of the tip of the arrow. The minimum value is
10.0 and the maximum value is 179.0. Any value smaller or
greater than this is contrained to the edge values.
"""
self.pad = pad
if head_width > 10:
self.head_width = 10
elif head_width < 0:
self.head_width = 0
else:
self.head_width = head_width

if head_angle >= 180:
self.head_angle = 179
elif head_angle < 10:
self.head_angle = 10
else:
self.head_angle = head_angle

def __call__(self, x0, y0, width, height, mutation_size):
# padding
Expand All @@ -2438,13 +2496,31 @@ def __call__(self, x0, y0, width, height, mutation_size):
dxx = dx / 2
x0 = x0 + pad / 1.4 # adjust by ~sqrt(2)

# The width adjustment is the value that must be added or
# subtracted from y_0 and y_1 for the ends of the head.
# The body width is 2dx.
# Subtracting 1 from the head_width gives what percentage of the
# body width is in the head, and there is .5x of that on each side.
# The .5 cancels out the 2dx for the body width.
width_adjustment = (self.head_width - 1) * dx

# The angle adjustment is the value that must be subtracted/added
# from x_0 or x_1 for the position of the tip.
# each half of the arrow head is a right angle triangle. Therefore,
# each half of the arrow head has the equation tan(head_angle/2)=
# (dx+width_adjustment)/(dxx+angle_adjustment).
angle_adjustment = ((dx + width_adjustment) / math.tan((self.
head_angle/2) * (math.pi/180))) - dxx

return Path._create_closed([
(x0 + dxx, y0), (x1, y0), # bot-segment
(x1, y0 - dxx), (x1 + dx + dxx, y0 + dx),
(x1, y1 + dxx), # right-arrow
(x1, y0 - width_adjustment),
(x1 + angle_adjustment + dxx, y0 + dx),
(x1, y1 + width_adjustment), # right-arrow
(x1, y1), (x0 + dxx, y1), # top-segment
(x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx),
(x0 + dxx, y0 - dxx), # left-arrow
(x0 + dxx, y1 + width_adjustment),
(x0 - angle_adjustment, y0 + dx),
(x0 + dxx, y0 - width_adjustment), # left-arrow
(x0 + dxx, y0)])

@_register_style(_style_list)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions lib/matplotlib/tests/test_arrow_patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,29 @@ def test_boxarrow():
bbox=dict(boxstyle=stylename, fc="w", ec="k"))


@image_comparison(['roadsign_test_image.png'])
def temp_test_boxarrow():

styles = mpatches.BoxStyle.get_styles()

n = len(styles)
spacing = 1.2

figheight = (n * spacing + .5)
fig = plt.figure(figsize=(4 / 1.5, figheight / 1.5))

fontsize = 0.3 * 72

for i, stylename in enumerate(sorted(styles)):
if stylename in ("larrow", "rarrow", "darrow"):
fig.text(0.5, ((n - i) * spacing - 0.5)/figheight, stylename,
ha="center",
size=fontsize,
transform=fig.transFigure,
bbox=dict(boxstyle=stylename+",head_width=1", fc="w",
ec="k"))


def __prepare_fancyarrow_dpi_cor_test():
"""
Convenience function that prepares and returns a FancyArrowPatch. It aims
Expand Down
10 changes: 5 additions & 5 deletions tutorials/text/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,10 @@
# Class Name Attrs
# ========== ============== ==========================
# Circle ``circle`` pad=0.3
# DArrow ``darrow`` pad=0.3
# DArrow ``darrow`` pad=0.3,head_width=1.5,head_angle=90
# Ellipse ``ellipse`` pad=0.3
# LArrow ``larrow`` pad=0.3
# RArrow ``rarrow`` pad=0.3
# LArrow ``larrow`` pad=0.3,head_width=1.5,head_angle=90
# RArrow ``rarrow`` pad=0.3,head_width=1.5,head_angle=90
# Round ``round`` pad=0.3,rounding_size=None
# Round4 ``round4`` pad=0.3,rounding_size=None
# Roundtooth ``roundtooth`` pad=0.3,tooth_size=None
Expand Down Expand Up @@ -254,8 +254,8 @@ def custom_box_style(x0, y0, width, height, mutation_size):
x0, y0 = x0 - pad, y0 - pad
x1, y1 = x0 + width, y0 + height
# return the new path
return Path([(x0, y0), (x1, y0), (x1, y1), (x0, y1),
(x0-pad, (y0+y1)/2), (x0, y0), (x0, y0)],
return Path([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y1-pad),
(x0-pad, (y0+y1)/2), (x0, y0+pad), (x0, y0), (x0, y0)],
closed=True)

fig, ax = plt.subplots(figsize=(3, 3))
Expand Down