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

Skip to content

Commit 77f1a4b

Browse files
authored
Merge pull request #29374 from story645/annotationbbox
DOC: Emphasize artist as annotation in AnnotationBbox demo and add to annotation guide
2 parents 78c9f7e + c58e81a commit 77f1a4b

4 files changed

Lines changed: 220 additions & 68 deletions

File tree

Lines changed: 111 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,99 @@
11
"""
2-
===================
3-
AnnotationBbox demo
4-
===================
5-
6-
`.AnnotationBbox` creates an annotation using an `.OffsetBox`, and
7-
provides more fine-grained control than `.Axes.annotate`. This example
8-
demonstrates the use of AnnotationBbox together with three different
9-
OffsetBoxes: `.TextArea`, `.DrawingArea`, and `.OffsetImage`.
2+
======================
3+
Artists as annotations
4+
======================
5+
6+
`.AnnotationBbox` facilitates using arbitrary artists as annotations, i.e. data at
7+
position *xy* is annotated by a box containing an artist at position *xybox*. The
8+
coordinate systems for these points are set via the *xycoords* and *boxcoords*
9+
parameters, respectively; see the *xycoords* and *textcoords* parameters of
10+
`.Axes.annotate` for a full listing of supported coordinate systems.
11+
The box containing the artist is a subclass of `.OffsetBox`, which is a container
12+
artist for positioning an artist relative to a parent artist.
1013
"""
14+
from pathlib import Path
15+
16+
import PIL
1117

1218
import matplotlib.pyplot as plt
1319
import numpy as np
1420

15-
from matplotlib.cbook import get_sample_data
21+
from matplotlib import get_data_path
1622
from matplotlib.offsetbox import AnnotationBbox, DrawingArea, OffsetImage, TextArea
17-
from matplotlib.patches import Circle
23+
from matplotlib.patches import Annulus, Circle, ConnectionPatch
1824

19-
fig, ax = plt.subplots()
25+
# %%%%
26+
# Text
27+
# ====
28+
#
29+
# `.AnnotationBbox` supports positioning annotations relative to data, Artists, and
30+
# callables, as described in :ref:`annotations`. The `.TextArea` is used to create a
31+
# textbox that is not explicitly attached to an axes, which allows it to be used for
32+
# annotating figure objects. When annotating an axes element (such as a plot) with text,
33+
# use `.Axes.annotate` because it will create the text artist for you.
2034

21-
# Define a 1st position to annotate (display it with a marker)
22-
xy = (0.5, 0.7)
23-
ax.plot(xy[0], xy[1], ".r")
35+
fig, axd = plt.subplot_mosaic([['t1', '.', 't2']], layout='compressed')
2436

25-
# Annotate the 1st position with a text box ('Test 1')
37+
# Define a 1st position to annotate (display it with a marker)
38+
xy1 = (.25, .75)
39+
xy2 = (.75, .25)
40+
axd['t1'].plot(*xy1, ".r")
41+
axd['t2'].plot(*xy2, ".r")
42+
axd['t1'].set(xlim=(0, 1), ylim=(0, 1), aspect='equal')
43+
axd['t2'].set(xlim=(0, 1), ylim=(0, 1), aspect='equal')
44+
45+
# Draw a connection patch arrow between the points
46+
c = ConnectionPatch(xyA=xy1, xyB=xy2,
47+
coordsA=axd['t1'].transData, coordsB=axd['t2'].transData,
48+
arrowstyle='->')
49+
fig.add_artist(c)
50+
51+
# Annotate the ConnectionPatch position ('Test 1')
2652
offsetbox = TextArea("Test 1")
2753

28-
ab = AnnotationBbox(offsetbox, xy,
29-
xybox=(-20, 40),
30-
xycoords='data',
31-
boxcoords="offset points",
32-
arrowprops=dict(arrowstyle="->"),
33-
bboxprops=dict(boxstyle="sawtooth"))
34-
ax.add_artist(ab)
35-
36-
# Annotate the 1st position with another text box ('Test')
37-
offsetbox = TextArea("Test")
38-
39-
ab = AnnotationBbox(offsetbox, xy,
40-
xybox=(1.02, xy[1]),
41-
xycoords='data',
42-
boxcoords=("axes fraction", "data"),
43-
box_alignment=(0., 0.5),
44-
arrowprops=dict(arrowstyle="->"))
45-
ax.add_artist(ab)
46-
47-
# Define a 2nd position to annotate (don't display with a marker this time)
48-
xy = [0.3, 0.55]
49-
50-
# Annotate the 2nd position with a circle patch
51-
da = DrawingArea(20, 20, 0, 0)
52-
p = Circle((10, 10), 10)
53-
da.add_artist(p)
54-
55-
ab = AnnotationBbox(da, xy,
56-
xybox=(1., xy[1]),
57-
xycoords='data',
58-
boxcoords=("axes fraction", "data"),
59-
box_alignment=(0.2, 0.5),
60-
arrowprops=dict(arrowstyle="->"),
61-
bboxprops=dict(alpha=0.5))
54+
# place the annotation above the midpoint of c
55+
ab1 = AnnotationBbox(offsetbox,
56+
xy=(.5, .5),
57+
xybox=(0, 30),
58+
xycoords=c,
59+
boxcoords="offset points",
60+
arrowprops=dict(arrowstyle="->"),
61+
bboxprops=dict(boxstyle="sawtooth"))
62+
fig.add_artist(ab1)
63+
64+
# %%%%
65+
# Images
66+
# ======
67+
# The `.OffsetImage` container facilitates using images as annotations
6268

63-
ax.add_artist(ab)
69+
fig, ax = plt.subplots()
70+
# Define a position to annotate
71+
xy = (0.3, 0.55)
72+
ax.scatter(*xy, s=200, marker='X')
6473

65-
# Annotate the 2nd position with an image (a generated array of pixels)
74+
# Annotate a position with an image generated from an array of pixels
6675
arr = np.arange(100).reshape((10, 10))
67-
im = OffsetImage(arr, zoom=2)
76+
im = OffsetImage(arr, zoom=2, cmap='viridis')
6877
im.image.axes = ax
6978

70-
ab = AnnotationBbox(im, xy,
79+
# place the image NW of xy
80+
ab = AnnotationBbox(im, xy=xy,
7181
xybox=(-50., 50.),
7282
xycoords='data',
7383
boxcoords="offset points",
7484
pad=0.3,
7585
arrowprops=dict(arrowstyle="->"))
76-
7786
ax.add_artist(ab)
7887

79-
# Annotate the 2nd position with another image (a Grace Hopper portrait)
80-
with get_sample_data("grace_hopper.jpg") as file:
81-
arr_img = plt.imread(file)
88+
# Annotate the position with an image from file (a Grace Hopper portrait)
89+
img_fp = Path(get_data_path(), "sample_data", "grace_hopper.jpg")
90+
with PIL.Image.open(img_fp) as arr_img:
91+
imagebox = OffsetImage(arr_img, zoom=0.2)
8292

83-
imagebox = OffsetImage(arr_img, zoom=0.2)
8493
imagebox.image.axes = ax
8594

86-
ab = AnnotationBbox(imagebox, xy,
95+
# place the image SE of xy
96+
ab = AnnotationBbox(imagebox, xy=xy,
8797
xybox=(120., -80.),
8898
xycoords='data',
8999
boxcoords="offset points",
@@ -96,8 +106,45 @@
96106
ax.add_artist(ab)
97107

98108
# Fix the display limits to see everything
99-
ax.set_xlim(0, 1)
100-
ax.set_ylim(0, 1)
109+
ax.set(xlim=(0, 1), ylim=(0, 1))
110+
111+
plt.show()
112+
113+
# %%%%
114+
# Arbitrary Artists
115+
# =================
116+
#
117+
# Multiple and arbitrary artists can be placed inside a `.DrawingArea`.
118+
119+
# make this the thumbnail image
120+
# sphinx_gallery_thumbnail_number = 3
121+
fig, ax = plt.subplots()
122+
123+
# Define a position to annotate
124+
xy = (0.05, 0.5)
125+
ax.scatter(*xy, s=500, marker='X')
126+
127+
# Annotate the position with a circle and annulus
128+
da = DrawingArea(120, 120)
129+
p = Circle((30, 30), 25, color='C0')
130+
da.add_artist(p)
131+
q = Annulus((65, 65), 50, 5, color='C1')
132+
da.add_artist(q)
133+
134+
135+
# Use the drawing area as an annotation
136+
ab = AnnotationBbox(da, xy=xy,
137+
xybox=(.55, xy[1]),
138+
xycoords='data',
139+
boxcoords=("axes fraction", "data"),
140+
box_alignment=(0, 0.5),
141+
arrowprops=dict(arrowstyle="->"),
142+
bboxprops=dict(alpha=0.5))
143+
144+
ax.add_artist(ab)
145+
146+
# Fix the display limits to see everything
147+
ax.set(xlim=(0, 1), ylim=(0, 1))
101148

102149
plt.show()
103150

@@ -108,11 +155,10 @@
108155
# The use of the following functions, methods, classes and modules is shown
109156
# in this example:
110157
#
111-
# - `matplotlib.patches.Circle`
112158
# - `matplotlib.offsetbox.TextArea`
113159
# - `matplotlib.offsetbox.DrawingArea`
114160
# - `matplotlib.offsetbox.OffsetImage`
115161
# - `matplotlib.offsetbox.AnnotationBbox`
116-
# - `matplotlib.cbook.get_sample_data`
117-
# - `matplotlib.pyplot.subplots`
118-
# - `matplotlib.pyplot.imread`
162+
#
163+
# .. tags::
164+
# component: annotation, styling: position

galleries/users_explain/text/annotations.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,50 @@ def __call__(self, x0, y0, width, height, mutation_size):
697697
# Note that, unlike in `.Legend`, the ``bbox_transform`` is set to
698698
# `.IdentityTransform` by default
699699
#
700+
# .. _annotations-bbox:
701+
#
702+
# Using an Artist as an annotation
703+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
704+
# `.AnnotationBbox` uses artists in `.OffsetBox` container artists as the annotations
705+
# and supports positioning these annotations using the same coordinate systems as the
706+
# other annotation methods. For more examples, see
707+
# :doc:`/gallery/text_labels_and_annotations/demo_annotation_box`
708+
709+
from matplotlib.offsetbox import AnnotationBbox, DrawingArea, OffsetImage
710+
from matplotlib.patches import Annulus
711+
712+
fig, ax = plt.subplots()
713+
714+
text = ax.text(.2, .8, "Green!", color='green')
715+
716+
da = DrawingArea(20, 20)
717+
annulus = Annulus((10, 10), 10, 5, color='tab:green')
718+
da.add_artist(annulus)
719+
720+
# position annulus relative to text
721+
ab1 = AnnotationBbox(da, xy=(.5, 0),
722+
xybox=(.5, .25),
723+
xycoords=text,
724+
boxcoords=(text, "data"),
725+
arrowprops=dict(arrowstyle="->"),
726+
bboxprops=dict(alpha=0.5))
727+
ax.add_artist(ab1)
728+
729+
N = 25
730+
arr = np.repeat(np.linspace(0, 1, N), N).reshape(N, N)
731+
im = OffsetImage(arr, cmap='Greens')
732+
im.image.axes = ax
733+
734+
# position gradient relative to text and annulus
735+
ab2 = AnnotationBbox(im, xy=(.5, 0),
736+
xybox=(.75, 0),
737+
xycoords=text,
738+
boxcoords=('data', annulus),
739+
arrowprops=dict(arrowstyle="->"),
740+
bboxprops=dict(alpha=0.5))
741+
ax.add_artist(ab2)
742+
743+
# %%%%
700744
# .. _annotating_coordinate_systems:
701745
#
702746
# Coordinate systems for annotations

lib/matplotlib/image.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,9 +1404,9 @@ class BboxImage(_ImageBase):
14041404
14051405
cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
14061406
The Colormap instance or registered colormap name used to map scalar
1407-
data to colors.
1407+
data to colors. This parameter is ignored if X is RGB(A).
14081408
norm : str or `~matplotlib.colors.Normalize`
1409-
Maps luminance to 0-1.
1409+
Maps luminance to 0-1. This parameter is ignored if X is RGB(A).
14101410
interpolation : str, default: :rc:`image.interpolation`
14111411
Supported values are 'none', 'auto', 'nearest', 'bilinear',
14121412
'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite',

lib/matplotlib/offsetbox.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1155,7 +1155,70 @@ def __init__(self, s, loc, *, pad=0.4, borderpad=0.5, prop=None, **kwargs):
11551155

11561156

11571157
class OffsetImage(OffsetBox):
1158+
"""
1159+
Container artist for images.
1160+
1161+
Image data is displayed using `.BboxImage`. This image is meant to be positioned
1162+
relative to a parent artist.
1163+
1164+
Parameters
1165+
----------
1166+
arr: array-like or `PIL.Image.Image`
1167+
The data to be color-coded. The interpretation depends on the
1168+
shape:
1169+
1170+
- (M, N) `~numpy.ndarray` or masked array: values to be colormapped
1171+
- (M, N, 3): RGB array
1172+
- (M, N, 4): RGBA array
1173+
1174+
zoom: float, default: 1
1175+
zoom factor:
1176+
1177+
- no zoom: factor =1
1178+
- zoom in: factor > 1
1179+
- zoom out: 0< factor < 1
1180+
1181+
cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
1182+
The Colormap instance or registered colormap name used to map scalar
1183+
data to colors. This parameter is ignored if X is RGB(A).
1184+
1185+
norm : str or `~matplotlib.colors.Normalize`, default: None
1186+
Maps luminance to 0-1. This parameter is ignored if X is RGB(A).
11581187
1188+
interpolation : str, default: :rc:`image.interpolation`
1189+
Supported values are 'none', 'auto', 'nearest', 'bilinear',
1190+
'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite',
1191+
'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell',
1192+
'sinc', 'lanczos', 'blackman'.
1193+
1194+
origin : {'upper', 'lower'}, default: :rc:`image.origin`
1195+
Place the [0, 0] index of the array in the upper left or lower left
1196+
corner of the Axes. The convention 'upper' is typically used for
1197+
matrices and images.
1198+
1199+
filternorm : bool, default: True
1200+
A parameter for the antigrain image resize filter
1201+
(see the antigrain documentation).
1202+
If filternorm is set, the filter normalizes integer values and corrects
1203+
the rounding errors. It doesn't do anything with the source floating
1204+
point values, it corrects only integers according to the rule of 1.0
1205+
which means that any sum of pixel weights must be equal to 1.0. So,
1206+
the filter function must produce a graph of the proper shape.
1207+
1208+
filterrad : float > 0, default: 4
1209+
The filter radius for filters that have a radius parameter, i.e. when
1210+
interpolation is one of: 'sinc', 'lanczos' or 'blackman'.
1211+
1212+
resample : bool, default: False
1213+
When True, use a full resampling method. When False, only resample when
1214+
the output image is larger than the input image.
1215+
1216+
dpi_cor: bool, default: True
1217+
Correct for the backend DPI setting
1218+
1219+
**kwargs : `.BboxImage` properties
1220+
1221+
"""
11591222
def __init__(self, arr, *,
11601223
zoom=1,
11611224
cmap=None,
@@ -1168,7 +1231,6 @@ def __init__(self, arr, *,
11681231
dpi_cor=True,
11691232
**kwargs
11701233
):
1171-
11721234
super().__init__()
11731235
self._dpi_cor = dpi_cor
11741236

0 commit comments

Comments
 (0)