8000 Merge branch 'NEW_AnchoredDirectionArrows' of github.com:idahj/matplo… · matplotlib/matplotlib@e751286 · GitHub
[go: up one dir, main page]

Skip to content

Commit e751286

Browse files
committed
Merge branch 'NEW_AnchoredDirectionArrows' of github.com:idahj/matplotlib into idahj_anchored_directionarrows
2 parents 3ba79fb + 6ec4f8f commit e751286

File tree

6 files changed

+344
-5
lines changed

6 files changed

+344
-5
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Add ``AnchoredDirectionArrows`` feature to mpl_toolkits
2+
--------------------------------------------------------
3+
4+
A new mpl_toolkits class
5+
:class:`~mpl_toolkits.axes_grid1.anchored_artists.AnchoredDirectionArrows`
6+
draws a pair of orthogonal arrows to inidcate directions on a 2D plot. A
7+
minimal working example takes in the transformation object for the coordinate
8+
system (typically ax.transAxes), and arrow labels. There are several optional
9+
parameters that can be used to alter layout. For example, the arrow pairs can
10+
be rotated and the color can be changed. By default the labels and arrows have
11+
the same color, but the class may also pass arguments for costumizing arrow
12+
and text layout, these are passed to :class:`matplotlib.text.TextPath` and
13+
`matplotlib.patches.FancyArrowPatch`. Location, length and width for both
14+
arrow tail and head can be adjusted, the the direction arrows and labels can
15+
have a frame. Padding and separation parameters can be adjusted.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""
2+
=============================
3+
Demo Anchored Direction Arrow
4+
=============================
5+
6+
"""
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
from mpl_toolkits.axes_grid1.anchored_artists import AnchoredDirectionArrows
10+
import matplotlib.font_manager as fm
11+
12+
fig, ax = plt.subplots()
13+
ax.imshow(np.random.random((10, 10)))
14+
15+
# Simple example
16+
simple_arrow = AnchoredDirectionArrows(ax.transAxes, 'X', 'Y')
17+
ax.add_artist(simple_arrow)
18+
19+
# High contrast arrow
20+
high_contrast_part_1 = AnchoredDirectionArrows(
21+
ax.transAxes,
22+
'111', r'11$\overline{2}$',
23+
loc=1,
24+
arrow_props={'ec': 'w', 'fc': 'none', 'alpha': 1,
25+
'lw': 2}
26+
)
27+
ax.add_artist(high_contrast_part_1)
28+
29+
high_contrast_part_2 = AnchoredDirectionArrows(
30+
ax.transAxes,
31+
'111', r'11$\overline{2}$',
32+
loc=1,
33+
arrow_props={'ec': 'none', 'fc': 'k'},
34+
text_props={'ec': 'w', 'fc': 'k', 'lw': 0.4}
35+
)
36+
ax.add_artist(high_contrast_part_2)
37+
38+
# Rotated arrow
39+
fontprops = fm.FontProperties(family='serif')
40+
41+
roatated_arrow = AnchoredDirectionArrows(
42+
ax.transAxes,
43+
'30', '120',
44+
loc='center',
45+
color='w',
46+
angle=30,
47+
fontproperties=fontprops
48+
)
49+
ax.add_artist(roatated_arrow)
50+
51+
# Altering arrow directions
52+
a1 = AnchoredDirectionArrows(
53+
ax.transAxes, 'A', 'B', loc='lower center',
54+
length=-0.15,
55+
sep_x=0.03, sep_y=0.03,
56+
color='r'
57+
)
58+
ax.add_artist(a1)
59+
60+
a2 = AnchoredDirectionArrows(
61+
ax.transAxes, 'A', ' B', loc='lower left',
62+
aspect_ratio=-1,
63+
sep_x=0.01, sep_y=-0.02,
64+
color='orange'
65+
)
66+
ax.add_artist(a2)
67+
68+
69+
a3 = AnchoredDirectionArrows(
70+
ax.transAxes, ' A', 'B', loc='lower right',
71+
length=-0.15,
72+
aspect_ratio=-1,
73+
sep_y=-0.1, sep_x=0.04,
74+
color='cyan'
75+
)
76+
ax.add_artist(a3)
77+
78+
plt.show()

lib/mpl_toolkits/axes_grid1/anchored_artists.py

Lines changed: 224 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
unicode_literals)
33
import six
44

5-
from matplotlib import docstring
5+
from matplotlib import docstring, transforms
66
from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox,
77
DrawingArea, TextArea, VPacker)
8-
from matplotlib.patches import Rectangle, Ellipse
9-
8+
from matplotlib.patches import (Rectangle, Ellipse, ArrowStyle,
9+
FancyArrowPatch, PathPatch)
10+
from matplotlib.text import TextPath
1011

1112
__all__ = ['AnchoredDrawingArea', 'AnchoredAuxTransformBox',
12-
'AnchoredEllipse', 'AnchoredSizeBar']
13+
'AnchoredEllipse', 'AnchoredSizeBar', 'AnchoredDirectionArrows']
1314

1415

1516
class AnchoredDrawingArea(AnchoredOffsetbox):
@@ -374,3 +375,222 @@ def __init__(self, transform, size, label, loc,
374375
child=self._box,
375376
prop=fontproperties,
376377
frameon=frameon, **kwargs)
378+
379+
380+
class AnchoredDirectionArrows(AnchoredOffsetbox):
381+
@docstring.dedent
382+
def __init__(self, transform, label_x, label_y, length=0.15,
383+
fontsize=0.08, loc=2, angle=0, aspect_ratio=1, pad=0.4,
384+
borderpad=0.4, frameon=False, color='w', alpha=1,
385+
sep_x=0.01, sep_y=0, fontproperties=None, back_length=0.15,
386+
head_width=10, head_length=15, tail_width=2,
387+
text_props=None, arrow_props=None,
388+
**kwargs):
389+
"""
390+
Draw two perpendicular arrows to indicate directions.
391+
392+
Parameters
393+
----------
394+
transform : `matplotlib.transforms.Transform`
395+
The transformation object for the coordinate system in use, i.e.,
396+
:attr:`matplotlib.axes.Axes.transAxes`.
397+
398+
label_x, label_y : string
399+
Label text for the x and y arrows
400+
401+
length : int or float, optional
402+
Length of the arrow, given in coordinates of
403+
*transform*.
404+
Defaults to 0.15.
405+
406+
fontsize : int, optional
407+
Size of label strings, given in coordinates of *transform*.
408+
Defaults to 0.08.
409+
410+
loc : int, optional
411+
Location of the direction arrows. Valid location codes are::
412+
413+
'upper right' : 1,
414+
'upper left' : 2,
415+
'lower left' : 3,
416+
'lower right' : 4,
417+
'right' : 5,
418+
'center left' : 6,
419+
'center right' : 7,
420+
'lower center' : 8,
421+
'upper center' : 9,
422+
'center' : 10
423+
424+
Defaults to 2.
425+
426+
angle : int or float, optional
427+
The angle of the arrows in degrees.
428+
Defaults to 0.
429+
430+
aspect_ratio : int or float, optional
431+
The ratio of the length of arrow_x and arrow_y.
432+
Negative numbers can be used to change the direction.
433+
Defaults to 1.
434+
435+
pad : int or float, optional
436+
Padding around the labels and arrows, in fraction of the font
437+
size. Defaults to 0.4.
438+
439+
borderpad : int or float, optional
440+
Border padding, in fraction of the font size.
441+
Defaults to 0.4.
442+
443+
frameon : bool, optional
444+
If True, draw a box around the arrows and labels.
445+
Defaults to False.
446+
447+
color : str, optional
448+
Color for the arrows and labels.
449+
Defaults to white.
450+
451+
alpha : int or float, optional
452+
Alpha values of the arrows and labels
453+
Defaults to 1.
454+
455+
sep_x, sep_y : int or float, optional
456+
Separation between the arrows and labels in coordinates of
457+
*transform*. Defaults to 0.01 and 0.
458+
459+
fontproperties : `matplotlib.font_manager.FontProperties`, optional
460+
Font properties for the label text.
461+
462+
back_length : float, optional
463+
Fraction of the arrow behind the arrow crossing.
464+
Defaults to 0.15.
465+
466+
head_width : int or float, optional
467+
Width of arrow head, sent to ArrowStyle.
468+
Defaults to 10.
469+
470+
head_length : int or float, optional
471+
Length of arrow head, sent to ArrowStyle.
472+
Defaults to 15.
473+
474+
tail_width : int or float, optional
475+
Width of arrow tail, sent to ArrowStyle.
476+
Defaults to 2.
477+
478+
text_props, arrow_props : dict
479+
Properties of the text and arrows, passed to
480+
:class:`matplotlib.text.TextPath` and
481+
`matplotlib.patches.FancyArrowPatch`
482+
483+
**kwargs :
484+
Keyworded arguments to pass to
485+
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
486+
487+
Attributes
488+
----------
489+
arrow_x, arrow_y : `matplotlib.patches.FancyArrowPatch`
490+
Arrow x and y
491+
492+
text_path_x, text_path_y : `matplotlib.text.TextPath`
493+
Path for arrow labels
494+
495+
p_x, p_y : `matplotlib.patches.PathPatch`
496+
Patch for arrow labels
497+
498+
box : `matplotlib.offsetbox.AuxTransformBox`
499+
Container for the arrows and labels.
500+
501+
Notes
502+
-----
503+
If *prop* is passed as a keyword argument, but *fontproperties* is
504+
not, then *prop* is be assumed to be the intended *fontproperties*.
505+
Using both *prop* and *fontproperties* is not supported.
506+
507+
Examples
508+
--------
509+
>>> import matplotlib.pyplot as plt
510+
>>> import numpy as np
511+
>>> from mpl_toolkits.axes_grid1.anchored_artists import \
512+
... AnchoredDirectionArrows
513+
>>> fig, ax = plt.subplots()
514+
>>> ax.imshow(np.random.random((10,10)))
515+
>>> arrows = AnchoredDirectionArrows(ax.transAxes, '111', '110')
516+
>>> ax.add_artist(arrows)
517+
>>> fig.show()
518+
519+
Using several of the optional parameters, creating downward pointing
520+
arrow and high contrast text labels.
521+
522+
>>> import matplotlib.font_manager as fm
523+
>>> fontprops = fm.FontProperties(family='monospace')
524+
>>> arrows = AnchoredDirectionArrows(ax.transAxes, 'East', 'South',
525+
... loc='lower left', color='k', aspect_ratio=-1, sep_x=0.02,
526+
... sep_y=-0.01, text_props={'ec':'w', 'fc':'k'},
527+
... fontproperties=fontprops)
528+
"""
529+
if arrow_props is None:
530+
arrow_props = {}
531+
532+
if text_props is None:
533+
text_props = {}
534+
535+
arrowstyle = ArrowStyle("Simple",
536+
head_width=head_width,
537+
head_length=head_length,
538+
tail_width=tail_width)
539+
540+
if fontproperties is None and 'prop' in kwargs:
541+
fontproperties = kwargs.pop('prop')
542+
543+
if 'color' not in arrow_props:
544+
arrow_props['color'] = color
545+
546+
if 'alpha' not in arrow_props:
547+
arrow_props['alpha'] = alpha
548+
549+
if 'color' not in text_props:
550+
text_props['color'] = color
551+
552+
if 'alpha' not in text_props:
553+
text_props['alpha'] = alpha
554+
555+
t_start = transform
556+
t_end = t_start + transforms.Affine2D().rotate_deg(angle)
5 F438 57+
558+
self.box = AuxTransformBox(t_end)
559+
560+
length_x = length
561+
length_y = length*aspect_ratio
562+
563+
self.arrow_x = FancyArrowPatch(
564+
(0, back_length*length_y),
565+
(length_x, back_length*length_y),
566+
arrowstyle=arrowstyle,
567+
shrinkA=0.0,
568+
shrinkB=0.0,
569+
**arrow_props)
570+
571+
self.arrow_y = FancyArrowPatch(
572+
(back_length*length_x, 0),
573+
(back_length*length_x, length_y),
574+
arrowstyle=arrowstyle,
575+
shrinkA=0.0,
576+
shrinkB=0.0,
577+
**arrow_props)
578+
579+
self.box.add_artist(self.arrow_x)
580+
self.box.add_artist(self.arrow_y)
581+
582+
text_path_x = TextPath((
583+
length_x+sep_x, back_length*length_y+sep_y), label_x,
584+
size=fontsize, prop=fontproperties)
585+
self.p_x = PathPatch(text_path_x, transform=t_start, **text_props)
586+
self.box.add_artist(self.p_x)
587+
588+
text_path_y = TextPath((
589+
length_x*back_length+sep_x, length_y*(1-back_length)+sep_y),
590+
label_y, size=fontsize, prop=fontproperties)
591+
self.p_y = PathPatch(text_path_y, **text_props)
592+
self.box.add_artist(self.p_y)
593+
594+
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
595+
child=self.box,
596+
frameon=frameon, **kwargs)
Loading

lib/mpl_toolkits/tests/test_axes_grid1.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
inset_axes,
1616
BboxConnectorPatch
1717
)
18-
from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBar
18+
from mpl_toolkits.axes_grid1.anchored_artists import (
19+
AnchoredSizeBar,
20+
AnchoredDirectionArrows)
1921

2022
from matplotlib.colors import LogNorm
2123
from matplotlib.transforms import Bbox, TransformedBbox, \
@@ -324,3 +326,27 @@ def test_zooming_with_inverted_axes():
324326
ax.axis([3, 1, 3, 1])
325327
inset_ax = zoomed_inset_axes(ax, zoom=2.5, loc=4)
326328
inset_ax.axis([1.4, 1.1, 1.4, 1.1])
329+
330+
331+
@image_comparison(baseline_images=['anchored_direction_arrows'],
332+
extensions=['png'])
333+
def test_anchored_direction_arrows():
334+
fig, ax = plt.subplots()
335+
ax.imshow(np.zeros((10, 10)))
336+
337+
simple_arrow = AnchoredDirectionArrows(ax.transAxes, 'X', 'Y')
338+
ax.add_artist(simple_arrow)
339+
340+
341+
@image_comparison(baseline_images=['anchored_direction_arrows_many_args'],
342+
extensions=['png'])
343+
def test_anchored_direction_arrows_many_args():
344+
fig, ax = plt.subplots()
345+
ax.imshow(np.ones((10, 10)))
346+
347+
direction_arrows = AnchoredDirectionArrows(
348+
ax.transAxes, 'A', 'B', loc='upper right', color='red',
349+
aspect_ratio=-0.5, pad=0.6, borderpad=2, frameon=True, alpha=0.7,
350+
sep_x=-0.06, sep_y=-0.08, back_length=0.1, head_width=9,
351+
head_length=10, tail_width=5)
352+
ax.add_artist(direction_arrows)

0 commit comments

Comments
 (0)
0