Skip to content

Commit

Permalink
Add tutorial about autoscaling (#18840)
Browse files Browse the repository at this point in the history
* Add tutorial about autoscaling

* Condensed  arguments formatting

* Language corrections.

* Apply suggestions from code review

Many thanks to @jklymak. I accept the suggestions as they are,
but I sense that there will be need for some code fixups.

Co-authored-by: Jody Klymak <jklymak@gmail.com>

* Fix code after applying suggestions.

* Apply all suggestions from code review

Will be revised.

Co-authored-by: Jody Klymak <jklymak@gmail.com>

* Fix flake8 formatting.

* Apply suggestions from code review

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Remove fig.show()

* Don't use "unchanged" use "default"

Co-authored-by: Jody Klymak <jklymak@gmail.com>
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
  • Loading branch information
3 people committed Dec 3, 2020
1 parent 39bf7ad commit d1dad03
Showing 1 changed file with 172 additions and 0 deletions.
172 changes: 172 additions & 0 deletions tutorials/intermediate/autoscale.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
"""
Autoscaling
===========
The limits on an axis can be set manually (e.g. ``ax.set_xlim(xmin, xmax)``)
or Matplotlib can set them automatically based on the data already on the axes.
There are a number of options to this autoscaling behaviour, discussed below.
"""

###############################################################################
# We will start with a simple line plot showing that autoscaling
# extends the axis limits 5% beyond the data limits (-2π, 2π).

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

x = np.linspace(-2 * np.pi, 2 * np.pi, 100)
y = np.sinc(x)

fig, ax = plt.subplots()
ax.plot(x, y)

###############################################################################
# Margins
# -------
# The default margin around the data limits is 5%:

ax.margins()

###############################################################################
# The margins can be made larger using `~matplotlib.axes.Axes.margins`:

fig, ax = plt.subplots()
ax.plot(x, y)
ax.margins(0.2, 0.2)

###############################################################################
# In general, margins can be in the range (-0.5, ∞), where negative margins set
# the axes limits to a subrange of the data range, i.e. they clip data.
# Using a single number for margins affects both axes, a single margin can be
# customized using keyword arguments ``x`` or ``y``, but positional and keyword
# interface cannot be combined.

fig, ax = plt.subplots()
ax.plot(x, y)
ax.margins(y=-0.2)

###############################################################################
# Sticky edges
# ------------
# There are plot elements (`.Artist`\s) that are usually used without margins.
# For example false-color images (e.g. created with `.Axes.imshow`) are not
# considered in the margins calculation.
#

xx, yy = np.meshgrid(x, x)
zz = np.sinc(np.sqrt((xx - 1)**2 + (yy - 1)**2))

fig, ax = plt.subplots(ncols=2, figsize=(12, 8))
ax[0].imshow(zz)
ax[0].set_title("default margins")
ax[1].imshow(zz)
ax[1].margins(0.2)
ax[1].set_title("margins(0.2)")

###############################################################################
# This override of margins is determined by "sticky edges", a
# property of `.Artist` class that can suppress adding margins to axis
# limits. The effect of sticky edges can be disabled on an Axes by changing
# `~matplotlib.axes.Axes.use_sticky_edges`.
# Artists have a property `.Artist.sticky_edges`, and the values of
# sticky edges can be changed by writing to ``Artist.sticky_edges.x`` or
# ``.Artist.sticky_edges.y``.
#
# The following example shows how overriding works and when it is needed.

fig, ax = plt.subplots(ncols=3, figsize=(16, 10))
ax[0].imshow(zz)
ax[0].margins(0.2)
ax[0].set_title("default use_sticky_edges\nmargins(0.2)")
ax[1].imshow(zz)
ax[1].margins(0.2)
ax[1].use_sticky_edges = False
ax[1].set_title("use_sticky_edges=False\nmargins(0.2)")
ax[2].imshow(zz)
ax[2].margins(-0.2)
ax[2].set_title("default use_sticky_edges\nmargins(-0.2)")

###############################################################################
# We can see that setting ``use_sticky_edges`` to *False* renders the image
# with requested margins.
#
# While sticky edges don't increase the axis limits through extra margins,
# negative margins are still taken into accout. This can be seen in
# the reduced limits of the third image.
#
# Controlling autoscale
# ---------------------
#
# By default, the limits are
# recalculated every time you add a new curve to the plot:

fig, ax = plt.subplots(ncols=2, figsize=(12, 8))
ax[0].plot(x, y)
ax[0].set_title("Single curve")
ax[1].plot(x, y)
ax[1].plot(x * 2.0, y)
ax[1].set_title("Two curves")

###############################################################################
# However, there are cases when you don't want to automatically adjust the
# viewport to new data.
#
# One way to disable autoscaling is to manually set the
# axis limit. Let's say that we want to see only a part of the data in
# greater detail. Setting the ``xlim`` persists even if we add more curves to
# the data. To recalculate the new limits calling `.Axes.autoscale` will
# toggle the functionality manually.

fig, ax = plt.subplots(ncols=2, figsize=(12, 8))
ax[0].plot(x, y)
ax[0].set_xlim(left=-1, right=1)
ax[0].plot(x + np.pi * 0.5, y)
ax[0].set_title("set_xlim(left=-1, right=1)\n")
ax[1].plot(x, y)
ax[1].set_xlim(left=-1, right=1)
ax[1].plot(x + np.pi * 0.5, y)
ax[1].autoscale()
ax[1].set_title("set_xlim(left=-1, right=1)\nautoscale()")

###############################################################################
# We can check that the first plot has autoscale disabled and that the second
# plot has it enabled again by using `.Axes.get_autoscale_on()`:

print(ax[0].get_autoscale_on()) # False means disabled
print(ax[1].get_autoscale_on()) # True means enabled -> recalculated

###############################################################################
# Arguments of the autoscale function give us precise control over the process
# of autoscaling. A combination of arguments ``enable``, and ``axis`` sets the
# autoscaling feature for the selected axis (or both). The argument ``tight``
# sets the margin of the selected axis to zero. To preserve settings of either
# ``enable`` or ``tight`` you can set the opposite one to *None*, that way
# it should not be modified. However, setting ``enable`` to *None* and tight
# to *True* affects both axes regardless of the ``axis`` argument.

fig, ax = plt.subplots()
ax.plot(x, y)
ax.margins(0.2, 0.2)
ax.autoscale(enable=None, axis="x", tight=True)

print(ax.margins())

###############################################################################
# Working with collections
# ------------------------
#
# Autoscale works out of the box for all lines, patches, and images added to
# the axes. One of the artists that it won't work with is a `.Collection`.
# After adding a collection to the axes, one has to manually trigger the
# `~matplotlib.axes.Axes.autoscale_view()` to recalculate
# axes limits.

fig, ax = plt.subplots()
collection = mpl.collections.StarPolygonCollection(
5, 0, [250, ], # five point star, zero angle, size 250px
offsets=np.column_stack([x, y]), # Set the positions
transOffset=ax.transData, # Propagate transformations of the Axes
)
ax.add_collection(collection)
ax.autoscale_view()

0 comments on commit d1dad03

Please sign in to comment.