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

Skip to content

Commit c58e81a

Browse files
story645timhoffmQuLogic
committed
breaks up annotationbbox demo, adds annotationbbox
to annotation guide, and documents the OffsetImage class Co-authored-by: Tim Hoffmann <[email protected]> Co-authored-by: Elliott Sales de Andrade <[email protected]>
1 parent 60dd2e2 commit c58e81a

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)