Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitbe2a1a3

Browse files
committed
Updated Angles on Bracket arrow styles example
1 parent6cc7cda commitbe2a1a3

File tree

1 file changed

+205
-12
lines changed

1 file changed

+205
-12
lines changed

‎doc/users/prev_whats_new/whats_new_3.4.0.rst

Lines changed: 205 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -371,22 +371,215 @@ Angles specified on the *Bracket* arrow styles (``]-[``, ``]-``, ``-[``, or
371371
applied. Previously, the *angleA* and *angleB* options were allowed, but did
372372
nothing.
373373

374+
Angles are annotated using ``AngleAnnotation`` from the example
375+
:doc:`/gallery/text_labels_and_annotations/angle_annotation`. `.FancyArrowPatch`
376+
arrows are added to show the directions of *angleA* and *angleB*.
377+
374378
..plot::
375379

376-
import matplotlib.patches as mpatches
380+
import matplotlib.pyplot as plt
381+
import numpy as np
382+
from matplotlib.patches import Arc, FancyArrowPatch
383+
from matplotlib.transforms import Bbox, IdentityTransform, TransformedBbox
377384

378-
fig, ax = plt.subplots()
379-
ax.set(xlim=(0, 1), ylim=(-1, 4))
380-
381-
for i, stylename in enumerate((']-[', '|-|')):
382-
for j, angle in enumerate([-30, 60]):
383-
arrowstyle = f'{stylename},angleA={angle},angleB={-angle}'
384-
patch = mpatches.FancyArrowPatch((0.1, 2*i + j), (0.9, 2*i + j),
385-
arrowstyle=arrowstyle,
386-
mutation_scale=25)
387-
ax.text(0.5, 2*i + j, arrowstyle,
388-
verticalalignment='bottom', horizontalalignment='center')
385+
386+
class AngleAnnotation(Arc):
387+
"""
388+
Draws an arc between two vectors which appears circular in display space.
389+
"""
390+
def __init__(self, xy, p1, p2, size=75, unit="points", ax=None,
391+
text="", textposition="inside", text_kw=None, **kwargs):
392+
"""
393+
Parameters
394+
----------
395+
xy, p1, p2 : tuple or array of two floats
396+
Center position and two points. Angle annotation is drawn between
397+
the two vectors connecting *p1* and *p2* with *xy*, respectively.
398+
Units are data coordinates.
399+
400+
size : float
401+
Diameter of the angle annotation in units specified by *unit*.
402+
403+
unit : str
404+
One of the following strings to specify the unit of *size*:
405+
406+
* "pixels": pixels
407+
* "points": points, use points instead of pixels to not have a
408+
dependence on the DPI
409+
* "axes width", "axes height": relative units of Axes width, height
410+
* "axes min", "axes max": minimum or maximum of relative Axes
411+
width, height
412+
413+
ax : `matplotlib.axes.Axes`
414+
The Axes to add the angle annotation to.
415+
416+
text : str
417+
The text to mark the angle with.
418+
419+
textposition : {"inside", "outside", "edge"}
420+
Whether to show the text in- or outside the arc. "edge" can be used
421+
for custom positions anchored at the arc's edge.
422+
423+
text_kw : dict
424+
Dictionary of arguments passed to the Annotation.
425+
426+
**kwargs
427+
Further parameters are passed to `matplotlib.patches.Arc`. Use this
428+
to specify, color, linewidth etc. of the arc.
429+
430+
"""
431+
self.ax = ax or plt.gca()
432+
self._xydata = xy # in data coordinates
433+
self.vec1 = p1
434+
self.vec2 = p2
435+
self.size = size
436+
self.unit = unit
437+
self.textposition = textposition
438+
439+
super().__init__(self._xydata, size, size, angle=0.0,
440+
theta1=self.theta1, theta2=self.theta2, **kwargs)
441+
442+
self.set_transform(IdentityTransform())
443+
self.ax.add_patch(self)
444+
445+
self.kw = dict(ha="center", va="center",
446+
xycoords=IdentityTransform(),
447+
xytext=(0, 0), textcoords="offset points",
448+
annotation_clip=True)
449+
self.kw.update(text_kw or {})
450+
self.text = ax.annotate(text, xy=self._center, **self.kw)
451+
452+
def get_size(self):
453+
factor = 1.
454+
if self.unit == "points":
455+
factor = self.ax.figure.dpi / 72.
456+
elif self.unit[:4] == "axes":
457+
b = TransformedBbox(Bbox.unit(), self.ax.transAxes)
458+
dic = {"max": max(b.width, b.height),
459+
"min": min(b.width, b.height),
460+
"width": b.width, "height": b.height}
461+
factor = dic[self.unit[5:]]
462+
return self.size * factor
463+
464+
def set_size(self, size):
465+
self.size = size
466+
467+
def get_center_in_pixels(self):
468+
"""return center in pixels"""
469+
return self.ax.transData.transform(self._xydata)
470+
471+
def set_center(self, xy):
472+
"""set center in data coordinates"""
473+
self._xydata = xy
474+
475+
def get_theta(self, vec):
476+
vec_in_pixels = self.ax.transData.transform(vec) - self._center
477+
return np.rad2deg(np.arctan2(vec_in_pixels[1], vec_in_pixels[0]))
478+
479+
def get_theta1(self):
480+
return self.get_theta(self.vec1)
481+
482+
def get_theta2(self):
483+
return self.get_theta(self.vec2)
484+
485+
def set_theta(self, angle):
486+
pass
487+
488+
# Redefine attributes of the Arc to always give values in pixel space
489+
_center = property(get_center_in_pixels, set_center)
490+
theta1 = property(get_theta1, set_theta)
491+
theta2 = property(get_theta2, set_theta)
492+
width = property(get_size, set_size)
493+
height = property(get_size, set_size)
494+
495+
# The following two methods are needed to update the text position.
496+
def draw(self, renderer):
497+
self.update_text()
498+
super().draw(renderer)
499+
500+
def update_text(self):
501+
c = self._center
502+
s = self.get_size()
503+
angle_span = (self.theta2 - self.theta1) % 360
504+
angle = np.deg2rad(self.theta1 + angle_span / 2)
505+
r = s / 2
506+
if self.textposition == "inside":
507+
r = s / np.interp(angle_span, [60, 90, 135, 180],
508+
[3.3, 3.5, 3.8, 4])
509+
self.text.xy = c + r * np.array([np.cos(angle), np.sin(angle)])
510+
if self.textposition == "outside":
511+
def R90(a, r, w, h):
512+
if a < np.arctan(h/2/(r+w/2)):
513+
return np.sqrt((r+w/2)**2 + (np.tan(a)*(r+w/2))**2)
514+
else:
515+
c = np.sqrt((w/2)**2+(h/2)**2)
516+
T = np.arcsin(c * np.cos(np.pi/2 - a + np.arcsin(h/2/c))/r)
517+
xy = r * np.array([np.cos(a + T), np.sin(a + T)])
518+
xy += np.array([w/2, h/2])
519+
return np.sqrt(np.sum(xy**2))
520+
521+
def R(a, r, w, h):
522+
aa = (a % (np.pi/4))*((a % (np.pi/2)) <= np.pi/4) + \
523+
(np.pi/4 - (a % (np.pi/4)))*((a % (np.pi/2)) >= np.pi/4)
524+
return R90(aa, r, *[w, h][::int(np.sign(np.cos(2*a)))])
525+
526+
bbox = self.text.get_window_extent()
527+
X = R(angle, r, bbox.width, bbox.height)
528+
trans = self.ax.figure.dpi_scale_trans.inverted()
529+
offs = trans.transform(((X-s/2), 0))[0] * 72
530+
self.text.set_position([offs*np.cos(angle), offs*np.sin(angle)])
531+
532+
533+
def get_point_of_rotated_vertical(origin, line_length, degrees):
534+
"""
535+
Return xy coordinates of the end of a vertical line rotated by degrees.
536+
"""
537+
rad = np.deg2rad(-degrees)
538+
return [origin[0] + line_length * np.sin(rad),
539+
origin[1] + line_length * np.cos(rad)]
540+
541+
542+
fig, ax = plt.subplots(figsize=(8, 7))
543+
ax.set(xlim=(0, 6), ylim=(-1, 4))
544+
545+
for i, stylename in enumerate(["]-[", "|-|"]):
546+
for j, angle in enumerate([-40, 60]):
547+
y = 2*i + j
548+
arrow_centers = [(1, y), (5, y)]
549+
vlines = [[c[0], y + 0.5] for c in arrow_centers]
550+
arrowstyle = f"{stylename},widthA=1.5,widthB=1.5,angleA={angle},angleB={-angle}"
551+
patch = FancyArrowPatch(arrow_centers[0], arrow_centers[1],
552+
arrowstyle=arrowstyle, mutation_scale=25)
389553
ax.add_patch(patch)
554+
ax.text(3, y + 0.05, arrowstyle.replace('widthA=1.5,widthB=1.5,', ''),
555+
verticalalignment="bottom", horizontalalignment="center")
556+
ax.vlines([i[0] for i in vlines], [y, y], [i[1] for i in vlines],
557+
linestyles="--", color="C0")
558+
# Get the coordinates for the drawn patches at A and B
559+
patch_top_coords = [get_point_of_rotated_vertical(arrow_centers[0], 0.5, angle),
560+
get_point_of_rotated_vertical(arrow_centers[1], 0.5, -angle)]
561+
# Create points for annotating A and B with AngleAnnotation
562+
# Points include the top of the vline and patch_top_coords
563+
pointsA =[(1, y + 0.5), patch_top_coords[0]]
564+
pointsB = [patch_top_coords[1], (5, y + 0.5)]
565+
# Define the directions for the arrows for the direction of AngleAnnotation
566+
arrow_angles = [0.5, -0.5]
567+
# Reverse the points and arrow_angles when the angle is negative
568+
if angle < 0:
569+
pointsA.reverse()
570+
pointsB.reverse()
571+
arrow_angles.reverse()
572+
# Add AngleAnnotation and arrows to show angle directions
573+
data = zip(arrow_centers, [pointsA, pointsB], vlines, arrow_angles,
574+
patch_top_coords)
575+
for center, points, vline, arrow_angle, patch_top in data:
576+
am = AngleAnnotation(center, points[0], points[1], ax=ax, size=0.25,
577+
unit="axes min", text=str(-angle))
578+
arrowstyle = "Simple, tail_width=0.5, head_width=4, head_length=8"
579+
kw = dict(arrowstyle=arrowstyle, color="C0")
580+
arrow = FancyArrowPatch(vline, patch_top,
581+
connectionstyle=f"arc3,rad={arrow_angle}", **kw)
582+
ax.add_patch(arrow)
390583
391584
``TickedStroke`` patheffect
392585
---------------------------

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp