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

Skip to content

Commit bc0882e

Browse files
authored
Merge pull request #11215 from jklymak/doc-transform-tut-dpi
DOC: add to transforms tutorial re fig.dpi_scale_trans
2 parents 427a981 + b81ca50 commit bc0882e

File tree

2 files changed

+161
-66
lines changed

2 files changed

+161
-66
lines changed

lib/matplotlib/axes/_base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,6 +1801,10 @@ def add_artist(self, a):
18011801
to manually update the dataLim if the artist is to be included in
18021802
autoscaling.
18031803
1804+
If no ``transform`` has been specified when creating the artist (e.g.
1805+
``artist.get_transform() == None``) then the transform is set to
1806+
``ax.transData``.
1807+
18041808
Returns the artist.
18051809
"""
18061810
a.axes = self

tutorials/advanced/transforms_tutorial.py

Lines changed: 157 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,59 @@
1818
``ax`` is a :class:`~matplotlib.axes.Axes` instance, and ``fig`` is a
1919
:class:`~matplotlib.figure.Figure` instance.
2020
21-
+-----------+-----------------------------+-----------------------------------+
22-
|Coordinates|Transformation object |Description |
23-
+-----------+-----------------------------+-----------------------------------+
24-
|"data" |``ax.transData`` |The coordinate system for the data,|
25-
| | |controlled by xlim and ylim. |
26-
+-----------+-----------------------------+-----------------------------------+
27-
|"axes" |``ax.transAxes`` |The coordinate system of the |
28-
| | |`~matplotlib.axes.Axes`; (0, 0) |
29-
| | |is bottom left of the axes, and |
30-
| | |(1, 1) is top right of the axes. |
31-
+-----------+-----------------------------+-----------------------------------+
32-
|"figure" |``fig.transFigure`` |The coordinate system of the |
33-
| | |`.Figure`; (0, 0) is bottom left |
34-
| | |of the figure, and (1, 1) is top |
35-
| | |right of the figure. |
36-
+-----------+-----------------------------+-----------------------------------+
37-
|"display" |``None``, or |The pixel coordinate system of the |
38-
| |``IdentityTransform()`` |display; (0, 0) is bottom left of |
39-
| | |the display, and (width, height) is|
40-
| | |top right of the display in pixels.|
41-
+-----------+-----------------------------+-----------------------------------+
42-
|"xaxis", |``ax.get_xaxis_transform()``,|Blended coordinate systems; use |
43-
|"yaxis" |``ax.get_yaxis_transform()`` |data coordinates on one of the axis|
44-
| | |and axes coordinates on the other. |
45-
+-----------+-----------------------------+-----------------------------------+
21+
+----------------+-----------------------------+-----------------------------------+
22+
|Coordinates |Transformation object |Description |
23+
+================+=============================+===================================+
24+
|"data" |``ax.transData`` |The coordinate system for the data,|
25+
| | |controlled by xlim and ylim. |
26+
+----------------+-----------------------------+-----------------------------------+
27+
|"axes" |``ax.transAxes`` |The coordinate system of the |
28+
| | |`~matplotlib.axes.Axes`; (0, 0) |
29+
| | |is bottom left of the axes, and |
30+
| | |(1, 1) is top right of the axes. |
31+
+----------------+-----------------------------+-----------------------------------+
32+
|"figure" |``fig.transFigure`` |The coordinate system of the |
33+
| | |`.Figure`; (0, 0) is bottom left |
34+
| | |of the figure, and (1, 1) is top |
35+
| | |right of the figure. |
36+
+----------------+-----------------------------+-----------------------------------+
37+
|"figure-inches" |``fig.dpi_scale_trans`` |The coordinate system of the |
38+
| | |`.Figure` in inches; (0, 0) is |
39+
| | |bottom left of the figure, and |
40+
| | |(width, height) is the top right |
41+
| | |of the figure in inches. |
42+
+----------------+-----------------------------+-----------------------------------+
43+
|"display" |``None``, or |The pixel coordinate system of the |
44+
| |``IdentityTransform()`` |display window; (0, 0) is bottom |
45+
| | |left of the window, and (width, |
46+
| | |height) is top right of the |
47+
| | |display window in pixels. |
48+
+----------------+-----------------------------+-----------------------------------+
49+
|"xaxis", |``ax.get_xaxis_transform()``,|Blended coordinate systems; use |
50+
|"yaxis" |``ax.get_yaxis_transform()`` |data coordinates on one of the axis|
51+
| | |and axes coordinates on the other. |
52+
+----------------+-----------------------------+-----------------------------------+
4653
4754
All of the transformation objects in the table above take inputs in
48-
their coordinate system, and transform the input to the `display`
49-
coordinate system. That is why the `display` coordinate system has
50-
`None` for the `Transformation Object` column -- it already is in
55+
their coordinate system, and transform the input to the ``display``
56+
coordinate system. That is why the ``display`` coordinate system has
57+
``None`` for the ``Transformation Object`` column -- it already is in
5158
display coordinates. The transformations also know how to invert
52-
themselves, to go from `display` back to the native coordinate system.
59+
themselves, to go from ``display`` back to the native coordinate system.
5360
This is particularly useful when processing events from the user
5461
interface, which typically occur in display space, and you want to
5562
know where the mouse click or key-press occurred in your data
5663
coordinate system.
5764
65+
Note that specifying objects in ``display`` coordinates will change their
66+
location if the ``dpi`` of the figure changes. This can cause confusion when
67+
printing or changing screen resolution, because the object can change location
68+
and size. Therefore it is most common
69+
for artists placed in an axes or figure to have their transform set to
70+
something *other* than the `~.transforms.IdentityTransform()`; the default when
71+
an artist is placed on an axes using `~.Axes.axes.add_artist` is for the
72+
transform to be ``ax.transData``.
73+
5874
.. _data-coords:
5975
6076
Data coordinates
@@ -71,6 +87,7 @@
7187

7288
import numpy as np
7389
import matplotlib.pyplot as plt
90+
import matplotlib.patches as mpatches
7491

7592
x = np.arange(0, 10, 0.005)
7693
y = np.exp(-x/2.) * np.sin(2*np.pi*x)
@@ -143,14 +160,12 @@
143160
(xdata, ydata), xytext=(-2*offset, offset), textcoords='offset points',
144161
bbox=bbox, arrowprops=arrowprops)
145162

146-
147163
disp = ax.annotate('display = (%.1f, %.1f)' % (xdisplay, ydisplay),
148164
(xdisplay, ydisplay), xytext=(0.5*offset, -offset),
149165
xycoords='figure pixels',
150166
textcoords='offset points',
151167
bbox=bbox, arrowprops=arrowprops)
152168

153-
154169
plt.show()
155170

156171
###############################################################################
@@ -229,15 +244,13 @@
229244
# move, but the circle will remain fixed because it is not in `data`
230245
# coordinates and will always remain at the center of the axes.
231246

232-
import matplotlib.patches as patches
233-
234247
fig = plt.figure()
235248
ax = fig.add_subplot(111)
236249
x, y = 10*np.random.rand(2, 1000)
237-
ax.plot(x, y, 'go') # plot some data in data coordinates
250+
ax.plot(x, y, 'go', alpha=0.2) # plot some data in data coordinates
238251

239-
circ = patches.Circle((0.5, 0.5), 0.25, transform=ax.transAxes,
240-
facecolor='yellow', alpha=0.5)
252+
circ = mpatches.Circle((0.5, 0.5), 0.25, transform=ax.transAxes,
253+
facecolor='blue', alpha=0.75)
241254
ax.add_patch(circ)
242255
plt.show()
243256

@@ -281,7 +294,7 @@
281294
# highlight the 1..2 stddev region with a span.
282295
# We want x to be in data coordinates and y to
283296
# span from 0..1 in axes coords
284-
rect = patches.Rectangle((1, 0), width=1, height=1,
297+
rect = mpatches.Rectangle((1, 0), width=1, height=1,
285298
transform=trans, color='yellow',
286299
alpha=0.5)
287300

@@ -303,12 +316,99 @@
303316
#
304317
# trans = ax.get_xaxis_transform()
305318
#
319+
# .. _transforms-fig-scale-dpi:
320+
#
321+
# Plotting in physical units
322+
# ==========================
323+
#
324+
# Sometimes we want an object to be a certain physical size on the plot.
325+
# Here we draw the same circle as above, but in physical units. If done
326+
# interactively, you can see that changing the size of the figure does
327+
# not change the offset of the circle from the lower-left corner,
328+
# does not change its size, and the circle remains a circle regardless of
329+
# the aspect ratio of the axes.
330+
331+
fig, ax = plt.subplots(figsize=(5, 4))
332+
x, y = 10*np.random.rand(2, 1000)
333+
ax.plot(x, y*10., 'go', alpha=0.2) # plot some data in data coordinates
334+
# add a circle in fixed-units
335+
circ = mpatches.Circle((2.5, 2), 1.0, transform=fig.dpi_scale_trans,
336+
facecolor='blue', alpha=0.75)
337+
ax.add_patch(circ)
338+
plt.show()
339+
340+
###############################################################################
341+
# If we change the figure size, the circle does not change its absolute
342+
# position and is cropped.
343+
344+
fig, ax = plt.subplots(figsize=(7, 2))
345+
x, y = 10*np.random.rand(2, 1000)
346+
ax.plot(x, y*10., 'go', alpha=0.2) # plot some data in data coordinates
347+
# add a circle in fixed-units
348+
circ = mpatches.Circle((2.5, 2), 1.0, transform=fig.dpi_scale_trans,
349+
facecolor='blue', alpha=0.75)
350+
ax.add_patch(circ)
351+
plt.show()
352+
353+
###############################################################################
354+
# Another use is putting a patch with a set physical dimension around a
355+
# data point on the axes. Here we add together two transforms. The
356+
# first sets the scaling of how large the ellipse should be and the second
357+
# sets its position. The ellipse is then placed at the origin, and then
358+
# we use the helper transform :class:`~matplotlib.transforms.ScaledTranslation`
359+
# to move it
360+
# to the right place in the ``ax.transData`` coordinate system.
361+
# This helper is instantiated with::
362+
#
363+
# trans = ScaledTranslation(xt, yt, scale_trans)
364+
#
365+
# where `xt` and `yt` are the translation offsets, and `scale_trans` is
366+
# a transformation which scales `xt` and `yt` at transformation time
367+
# before applying the offsets.
368+
#
369+
# Note the use of the plus operator on the transforms below.
370+
# This code says: first apply the scale transformation ``fig.dpi_scale_trans``
371+
# to make the ellipse the proper size, but still centered at (0, 0),
372+
# and then translate the data to `xdata[0]` and `ydata[0]` in data space.
373+
#
374+
# In interactive use, the ellipse stays the same size even if the
375+
# axes limits are changed via zoom.
376+
#
377+
378+
fig, ax = plt.subplots()
379+
xdata, ydata = (0.2, 0.7), (0.5, 0.5)
380+
ax.plot(xdata, ydata, "o")
381+
ax.set_xlim((0, 1))
382+
383+
trans = (fig.dpi_scale_trans +
384+
transforms.ScaledTranslation(xdata[0], ydata[0], ax.transData))
385+
386+
# plot an ellipse around the point that is 150 x 130 points in diameter...
387+
circle = mpatches.Ellipse((0, 0), 150/72, 130/72, angle=40,
388+
fill=None, transform=trans)
389+
ax.add_patch(circle)
390+
plt.show()
391+
392+
###############################################################################
393+
# .. note::
394+
#
395+
# The order of transformation matters. Here the ellipse
396+
# is given the right dimensions in display space *first* and then moved
397+
# in data space to the correct spot.
398+
# If we had done the ``ScaledTranslation`` first, then
399+
# ``xdata[0]`` and ``ydata[0]`` would
400+
# first be transformed to ``display`` coordinates (``[ 358.4 475.2]`` on
401+
# a 200-dpi monitor) and then those coordinates
402+
# would be scaled by ``fig.dpi_scale_trans`` pushing the center of
403+
# the ellipse well off the screen (i.e. ``[ 71680. 95040.]``).
404+
#
306405
# .. _offset-transforms-shadow:
307406
#
308407
# Using offset transforms to create a shadow effect
309408
# =================================================
310409
#
311-
# One use of transformations is to create a new transformation that is
410+
# Another use of :class:`~matplotlib.transforms.ScaledTranslation` is to create
411+
# a new transformation that is
312412
# offset from another transformation, e.g., to place one object shifted a
313413
# bit relative to another object. Typically you want the shift to be in
314414
# some physical dimension, like points or inches rather than in data
@@ -318,38 +418,17 @@
318418
# One use for an offset is to create a shadow effect, where you draw one
319419
# object identical to the first just to the right of it, and just below
320420
# it, adjusting the zorder to make sure the shadow is drawn first and
321-
# then the object it is shadowing above it. The transforms module has a
322-
# helper transformation
323-
# :class:`~matplotlib.transforms.ScaledTranslation`. It is
324-
# instantiated with::
421+
# then the object it is shadowing above it.
325422
#
326-
# trans = ScaledTranslation(xt, yt, scale_trans)
327-
#
328-
# where `xt` and `yt` are the translation offsets, and `scale_trans` is
329-
# a transformation which scales `xt` and `yt` at transformation time
330-
# before applying the offsets. A typical use case is to use the figure
331-
# ``fig.dpi_scale_trans`` transformation for the `scale_trans` argument,
332-
# to first scale `xt` and `yt` specified in points to `display` space
333-
# before doing the final offset. The dpi and inches offset is a
334-
# common-enough use case that we have a special helper function to
335-
# create it in :func:`matplotlib.transforms.offset_copy`, which returns
336-
# a new transform with an added offset. But in the example below, we'll
337-
# create the offset transform ourselves. Note the use of the plus
338-
# operator in::
339-
#
340-
# offset = transforms.ScaledTranslation(dx, dy,
341-
# fig.dpi_scale_trans)
342-
# shadow_transform = ax.transData + offset
343-
#
344-
# showing that can chain transformations using the addition operator.
345-
# This code says: first apply the data transformation ``ax.transData``
346-
# and then translate the data by `dx` and `dy` points. In typography,
423+
# Here we apply the transforms in the *opposite* order to the use of
424+
# :class:`~matplotlib.transforms.ScaledTranslation` above. The plot is
425+
# first made in data units (``ax.transData``) and then shifted by
426+
# ``dx`` and ``dy`` points using `fig.dpi_scale_trans`. (In typography,
347427
# a`point <https://en.wikipedia.org/wiki/Point_%28typography%29>`_ is
348428
# 1/72 inches, and by specifying your offsets in points, your figure
349-
# will look the same regardless of the dpi resolution it is saved in.
429+
# will look the same regardless of the dpi resolution it is saved in.)
350430

351-
fig = plt.figure()
352-
ax = fig.add_subplot(111)
431+
fig, ax = plt.subplots()
353432

354433
# make a simple sine wave
355434
x = np.arange(0., 2., 0.01)
@@ -370,7 +449,19 @@
370449
ax.set_title('creating a shadow effect with an offset transform')
371450
plt.show()
372451

452+
373453
###############################################################################
454+
# .. note::
455+
#
456+
# The dpi and inches offset is a
457+
# common-enough use case that we have a special helper function to
458+
# create it in :func:`matplotlib.transforms.offset_copy`, which returns
459+
# a new transform with an added offset. So above we could have done::
460+
#
461+
# shadow_transform = transforms.offset_copy(ax.transData,
462+
# fig=fig, dx, dy, units='inches')
463+
#
464+
#
374465
# .. _transformation-pipeline:
375466
#
376467
# The transformation pipeline

0 commit comments

Comments
 (0)