Skip to content
Merged
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
176 changes: 111 additions & 65 deletions galleries/examples/text_labels_and_annotations/demo_annotation_box.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,99 @@
"""
===================
AnnotationBbox demo
===================

`.AnnotationBbox` creates an annotation using an `.OffsetBox`, and
provides more fine-grained control than `.Axes.annotate`. This example
demonstrates the use of AnnotationBbox together with three different
OffsetBoxes: `.TextArea`, `.DrawingArea`, and `.OffsetImage`.
======================
Artists as annotations
======================

`.AnnotationBbox` facilitates using arbitrary artists as annotations, i.e. data at
position *xy* is annotated by a box containing an artist at position *xybox*. The
coordinate systems for these points are set via the *xycoords* and *boxcoords*
parameters, respectively; see the *xycoords* and *textcoords* parameters of
`.Axes.annotate` for a full listing of supported coordinate systems.
The box containing the artist is a subclass of `.OffsetBox`, which is a container
artist for positioning an artist relative to a parent artist.
"""
from pathlib import Path

import PIL

import matplotlib.pyplot as plt
import numpy as np

from matplotlib.cbook import get_sample_data
from matplotlib import get_data_path
from matplotlib.offsetbox import AnnotationBbox, DrawingArea, OffsetImage, TextArea
from matplotlib.patches import Circle
from matplotlib.patches import Annulus, Circle, ConnectionPatch

fig, ax = plt.subplots()
# %%%%
# Text
# ====
#
# `.AnnotationBbox` supports positioning annotations relative to data, Artists, and
Comment thread
story645 marked this conversation as resolved.
# callables, as described in :ref:`annotations`. The `.TextArea` is used to create a
# textbox that is not explicitly attached to an axes, which allows it to be used for
# annotating figure objects. When annotating an axes element (such as a plot) with text,
# use `.Axes.annotate` because it will create the text artist for you.

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

# Annotate the 1st position with a text box ('Test 1')
# Define a 1st position to annotate (display it with a marker)
xy1 = (.25, .75)
xy2 = (.75, .25)
axd['t1'].plot(*xy1, ".r")
axd['t2'].plot(*xy2, ".r")
axd['t1'].set(xlim=(0, 1), ylim=(0, 1), aspect='equal')
axd['t2'].set(xlim=(0, 1), ylim=(0, 1), aspect='equal')

# Draw a connection patch arrow between the points
c = ConnectionPatch(xyA=xy1, xyB=xy2,
coordsA=axd['t1'].transData, coordsB=axd['t2'].transData,
arrowstyle='->')
fig.add_artist(c)

# Annotate the ConnectionPatch position ('Test 1')
offsetbox = TextArea("Test 1")

ab = AnnotationBbox(offsetbox, xy,
xybox=(-20, 40),
xycoords='data',
boxcoords="offset points",
arrowprops=dict(arrowstyle="->"),
bboxprops=dict(boxstyle="sawtooth"))
ax.add_artist(ab)

# Annotate the 1st position with another text box ('Test')
offsetbox = TextArea("Test")

ab = AnnotationBbox(offsetbox, xy,
xybox=(1.02, xy[1]),
xycoords='data',
boxcoords=("axes fraction", "data"),
box_alignment=(0., 0.5),
arrowprops=dict(arrowstyle="->"))
ax.add_artist(ab)

# Define a 2nd position to annotate (don't display with a marker this time)
xy = [0.3, 0.55]

# Annotate the 2nd position with a circle patch
da = DrawingArea(20, 20, 0, 0)
p = Circle((10, 10), 10)
da.add_artist(p)

ab = AnnotationBbox(da, xy,
xybox=(1., xy[1]),
xycoords='data',
boxcoords=("axes fraction", "data"),
box_alignment=(0.2, 0.5),
arrowprops=dict(arrowstyle="->"),
bboxprops=dict(alpha=0.5))
# place the annotation above the midpoint of c
ab1 = AnnotationBbox(offsetbox,
xy=(.5, .5),
xybox=(0, 30),
xycoords=c,
boxcoords="offset points",
arrowprops=dict(arrowstyle="->"),
Comment thread
story645 marked this conversation as resolved.
bboxprops=dict(boxstyle="sawtooth"))
fig.add_artist(ab1)

# %%%%
# Images
# ======
# The `.OffsetImage` container facilitates using images as annotations

ax.add_artist(ab)
fig, ax = plt.subplots()
# Define a position to annotate
xy = (0.3, 0.55)
ax.scatter(*xy, s=200, marker='X')

# Annotate the 2nd position with an image (a generated array of pixels)
# Annotate a position with an image generated from an array of pixels
arr = np.arange(100).reshape((10, 10))
im = OffsetImage(arr, zoom=2)
im = OffsetImage(arr, zoom=2, cmap='viridis')
im.image.axes = ax

ab = AnnotationBbox(im, xy,
# place the image NW of xy
ab = AnnotationBbox(im, xy=xy,
xybox=(-50., 50.),
xycoords='data',
boxcoords="offset points",
pad=0.3,
arrowprops=dict(arrowstyle="->"))

ax.add_artist(ab)

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

imagebox = OffsetImage(arr_img, zoom=0.2)
imagebox.image.axes = ax

ab = AnnotationBbox(imagebox, xy,
# place the image SE of xy
ab = AnnotationBbox(imagebox, xy=xy,
xybox=(120., -80.),
xycoords='data',
boxcoords="offset points",
Expand All @@ -96,8 +106,45 @@
ax.add_artist(ab)

# Fix the display limits to see everything
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.set(xlim=(0, 1), ylim=(0, 1))

plt.show()

# %%%%
# Arbitrary Artists
# =================
#
# Multiple and arbitrary artists can be placed inside a `.DrawingArea`.

# make this the thumbnail image
# sphinx_gallery_thumbnail_number = 3
fig, ax = plt.subplots()

# Define a position to annotate
xy = (0.05, 0.5)
ax.scatter(*xy, s=500, marker='X')

# Annotate the position with a circle and annulus
da = DrawingArea(120, 120)
p = Circle((30, 30), 25, color='C0')
da.add_artist(p)
q = Annulus((65, 65), 50, 5, color='C1')
da.add_artist(q)


# Use the drawing area as an annotation
ab = AnnotationBbox(da, xy=xy,
xybox=(.55, xy[1]),
xycoords='data',
boxcoords=("axes fraction", "data"),
box_alignment=(0, 0.5),
arrowprops=dict(arrowstyle="->"),
bboxprops=dict(alpha=0.5))

ax.add_artist(ab)

# Fix the display limits to see everything
ax.set(xlim=(0, 1), ylim=(0, 1))

plt.show()

Expand All @@ -108,11 +155,10 @@
# The use of the following functions, methods, classes and modules is shown
# in this example:
#
# - `matplotlib.patches.Circle`
# - `matplotlib.offsetbox.TextArea`
# - `matplotlib.offsetbox.DrawingArea`
# - `matplotlib.offsetbox.OffsetImage`
# - `matplotlib.offsetbox.AnnotationBbox`
# - `matplotlib.cbook.get_sample_data`
# - `matplotlib.pyplot.subplots`
# - `matplotlib.pyplot.imread`
#
# .. tags::
# component: annotation, styling: position
44 changes: 44 additions & 0 deletions galleries/users_explain/text/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,50 @@ def __call__(self, x0, y0, width, height, mutation_size):
# Note that, unlike in `.Legend`, the ``bbox_transform`` is set to
# `.IdentityTransform` by default
#
# .. _annotations-bbox:
#
# Using an Artist as an annotation
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# `.AnnotationBbox` uses artists in `.OffsetBox` container artists as the annotations
# and supports positioning these annotations using the same coordinate systems as the
# other annotation methods. For more examples, see
# :doc:`/gallery/text_labels_and_annotations/demo_annotation_box`

from matplotlib.offsetbox import AnnotationBbox, DrawingArea, OffsetImage
from matplotlib.patches import Annulus

fig, ax = plt.subplots()

text = ax.text(.2, .8, "Green!", color='green')

da = DrawingArea(20, 20)
annulus = Annulus((10, 10), 10, 5, color='tab:green')
da.add_artist(annulus)

# position annulus relative to text
ab1 = AnnotationBbox(da, xy=(.5, 0),
xybox=(.5, .25),
xycoords=text,
boxcoords=(text, "data"),
arrowprops=dict(arrowstyle="->"),
bboxprops=dict(alpha=0.5))
ax.add_artist(ab1)

N = 25
arr = np.repeat(np.linspace(0, 1, N), N).reshape(N, N)
im = OffsetImage(arr, cmap='Greens')
im.image.axes = ax

# position gradient relative to text and annulus
ab2 = AnnotationBbox(im, xy=(.5, 0),
xybox=(.75, 0),
xycoords=text,
boxcoords=('data', annulus),
arrowprops=dict(arrowstyle="->"),
bboxprops=dict(alpha=0.5))
ax.add_artist(ab2)

# %%%%
# .. _annotating_coordinate_systems:
#
# Coordinate systems for annotations
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1404,9 +1404,9 @@ class BboxImage(_ImageBase):

cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
The Colormap instance or registered colormap name used to map scalar
data to colors.
data to colors. This parameter is ignored if X is RGB(A).
norm : str or `~matplotlib.colors.Normalize`
Maps luminance to 0-1.
Maps luminance to 0-1. This parameter is ignored if X is RGB(A).
interpolation : str, default: :rc:`image.interpolation`
Supported values are 'none', 'auto', 'nearest', 'bilinear',
'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite',
Expand Down
64 changes: 63 additions & 1 deletion lib/matplotlib/offsetbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -1155,7 +1155,70 @@ def __init__(self, s, loc, *, pad=0.4, borderpad=0.5, prop=None, **kwargs):


class OffsetImage(OffsetBox):
"""
Container artist for images.

Image data is displayed using `.BboxImage`. This image is meant to be positioned
relative to a parent artist.

Parameters
----------
arr: array-like or `PIL.Image.Image`
The data to be color-coded. The interpretation depends on the
shape:

- (M, N) `~numpy.ndarray` or masked array: values to be colormapped
- (M, N, 3): RGB array
- (M, N, 4): RGBA array

zoom: float, default: 1
zoom factor:

- no zoom: factor =1
- zoom in: factor > 1
- zoom out: 0< factor < 1

cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap`
The Colormap instance or registered colormap name used to map scalar
data to colors. This parameter is ignored if X is RGB(A).

norm : str or `~matplotlib.colors.Normalize`, default: None
Maps luminance to 0-1. This parameter is ignored if X is RGB(A).

interpolation : str, default: :rc:`image.interpolation`
Supported values are 'none', 'auto', 'nearest', 'bilinear',
'bicubic', 'spline16', 'spline36', 'hanning', 'hamming', 'hermite',
'kaiser', 'quadric', 'catrom', 'gaussian', 'bessel', 'mitchell',
'sinc', 'lanczos', 'blackman'.

origin : {'upper', 'lower'}, default: :rc:`image.origin`
Place the [0, 0] index of the array in the upper left or lower left
corner of the Axes. The convention 'upper' is typically used for
matrices and images.

filternorm : bool, default: True
A parameter for the antigrain image resize filter
(see the antigrain documentation).
If filternorm is set, the filter normalizes integer values and corrects
the rounding errors. It doesn't do anything with the source floating
point values, it corrects only integers according to the rule of 1.0
which means that any sum of pixel weights must be equal to 1.0. So,
the filter function must produce a graph of the proper shape.

filterrad : float > 0, default: 4
The filter radius for filters that have a radius parameter, i.e. when
interpolation is one of: 'sinc', 'lanczos' or 'blackman'.

resample : bool, default: False
When True, use a full resampling method. When False, only resample when
the output image is larger than the input image.

dpi_cor: bool, default: True
Correct for the backend DPI setting

**kwargs : `.BboxImage` properties

"""
def __init__(self, arr, *,
zoom=1,
cmap=None,
Expand All @@ -1168,7 +1231,6 @@ def __init__(self, arr, *,
dpi_cor=True,
**kwargs
):

super().__init__()
self._dpi_cor = dpi_cor

Expand Down
Loading