From cd47ecf66b11c6337ecaf1e24a578497cd29e170 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 19 Nov 2017 10:59:32 -0800 Subject: [PATCH] DOC: add more tutorial to text/text_intro --- lib/matplotlib/ticker.py | 6 +- tutorials/text/text_intro.py | 379 +++++++++++++++++++++++++++++++++-- 2 files changed, 363 insertions(+), 22 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index d54adc7d0a6a..c6c813e1b6d5 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1804,7 +1804,11 @@ def __init__(self, *args, **kwargs): *steps* Sequence of nice numbers starting with 1 and ending with 10; - e.g., [1, 2, 4, 5, 10] + e.g., [1, 2, 4, 5, 10], where the values are acceptable + tick multiples. i.e. for the example, 20, 40, 60 would be + an acceptable set of ticks, as would 0.4, 0.6, 0.8, because + they are multiples of 2. However, 30, 60, 90 would not + be allowed because 3 does not appear in the list of steps. *integer* If True, ticks will take only integer values, provided diff --git a/tutorials/text/text_intro.py b/tutorials/text/text_intro.py index cc23b1462035..e8a04860db53 100644 --- a/tutorials/text/text_intro.py +++ b/tutorials/text/text_intro.py @@ -1,6 +1,7 @@ """ -Text introduction -================= +======================== +Text in Matplotlib Plots +======================== Introduction to plotting and working with text in Matplotlib. @@ -31,36 +32,38 @@ =================== The following commands are used to create text in the pyplot -interface +interface and the object-oriented API: -* :func:`~matplotlib.pyplot.text` - add text at an arbitrary location to the ``Axes``; - :meth:`matplotlib.axes.Axes.text` in the API. +* `matplotlib.pyplot.text` - add text at an arbitrary location to the Axes; + `matplotlib.axes.Axes.text` in the API. -* :func:`~matplotlib.pyplot.xlabel` - add a label to the x-axis; - :meth:`matplotlib.axes.Axes.set_xlabel` in the API. +* `matplotlib.pyplot.xlabel` - add a label to the x-axis; + `matplotlib.axes.Axes.set_xlabel` in the API. -* :func:`~matplotlib.pyplot.ylabel` - add a label to the y-axis; - :meth:`matplotlib.axes.Axes.set_ylabel` in the API. +* `matplotlib.pyplot.ylabel` - add a label to the y-axis; + `matplotlib.axes.Axes.set_ylabel` in the API. -* :func:`~matplotlib.pyplot.title` - add a title to the ``Axes``; - :meth:`matplotlib.axes.Axes.set_title` in the API. +* `matplotlib.pyplot.title` - add a title to the `~matplotlib.axes.Axes`; + `matplotlib.axes.Axes.set_title` in the API. -* :func:`~matplotlib.pyplot.figtext` - add text at an arbitrary location to the ``Figure``; - :meth:`matplotlib.figure.Figure.text` in the API. +* `matplotlib.pyplot.figtext` - add text at an arbitrary location to the + `~matplotlib.figure.Figure`; `matplotlib.figure.Figure.text` in the API. -* :func:`~matplotlib.pyplot.suptitle` - add a title to the ``Figure``; - :meth:`matplotlib.figure.Figure.suptitle` in the API. +* `matplotlib.pyplot.suptitle` - add a title to the + `~matplotlib.figure.Figure`; `matplotlib.figure.Figure.suptitle` in the API. -* :func:`~matplotlib.pyplot.annotate` - add an annotation, with - optional arrow, to the ``Axes`` ; :meth:`matplotlib.axes.Axes.annotate` - in the API. +* `matplotlib.pyplot.annotate` - add an annotation, with + optional arrow, to the `~matplotlib.axes.Axes`; + `matplotlib.axes.Axes.annotate` in the API. All of these functions create and return a -:func:`matplotlib.text.Text` instance, which can be configured with a +`matplotlib.text.Text` instance, which can be configured with a variety of font and other properties. The example below shows all of -these commands in action. +these commands in action, and more detail is provided in the sections that +follow. """ +import matplotlib import matplotlib.pyplot as plt fig = plt.figure() @@ -78,7 +81,7 @@ ax.text(2, 6, r'an equation: $E=mc^2$', fontsize=15) -ax.text(3, 2, u'unicode: Institut f\374r Festk\366rperphysik') +ax.text(3, 2, u'unicode: Institut für Festkörperphysik') ax.text(0.95, 0.01, 'colored text in axes coords', verticalalignment='bottom', horizontalalignment='right', @@ -93,3 +96,337 @@ ax.axis([0, 10, 0, 10]) plt.show() + +############################################################################### +# Labels for x- and y-axis +# ======================== +# +# Specifying the labels for the x- and y-axis is strightforward, via the +# `~matplotlib.axes.Axes.set_xlabel` and +# `~matplotlib.axes.Axes.set_ylabel` methods. + +import matplotlib.pyplot as plt +import numpy as np + +x1 = np.linspace(0.0, 5.0, 100) +y1 = np.cos(2 * np.pi * x1) * np.exp(-x1) + +fig, ax = plt.subplots(figsize=(5, 3)) +fig.subplots_adjust(bottom=0.15, left=0.2) +ax.plot(x1, y1) +ax.set_xlabel('time [s]') +ax.set_ylabel('Damped oscillation [V]') + +plt.show() + +############################################################################### +# The x- and y-labels are automatically placed so that they clear the x- and +# y-ticklabels. Compare the plot below with that above, and note the y-label +# is to the left of the one above. + +fig, ax = plt.subplots(figsize=(5, 3)) +fig.subplots_adjust(bottom=0.15, left=0.2) +ax.plot(x1, y1*10000) +ax.set_xlabel('time [s]') +ax.set_ylabel('Damped oscillation [V]') + +plt.show() + +############################################################################### +# If you want to move the labels, you can specify the *labelpad* kyeword +# argument, where the value is points (1/72", the same unit used to specify +# fontsizes). + +fig, ax = plt.subplots(figsize=(5, 3)) +fig.subplots_adjust(bottom=0.15, left=0.2) +ax.plot(x1, y1*10000) +ax.set_xlabel('time [s]') +ax.set_ylabel('Damped oscillation [V]', labelpad=18) + +plt.show() + +############################################################################### +# Or, the labels accept all the `~matplotlib.text.Text` keyword arguments, +# including *position*, via which we can manually specify the label positions. +# Here we put the xlabel to the far left of the axis. Note, that the +# y-coordinate of this position has no effect - to adjust the y-position we +# need to use the *labelpad* kwarg. + +fig, ax = plt.subplots(figsize=(5, 3)) +fig.subplots_adjust(bottom=0.15, left=0.2) +ax.plot(x1, y1) +ax.set_xlabel('time [s]', position=(0., 1e6), + horizontalalignment='left') +ax.set_ylabel('Damped oscillation [V]') + +plt.show() + +############################################################################## +# All the labelling in this tutorial can be changed by manipulating the +# `matplotlib.font_manager.FontProperties` method, or by named kwargs to +# `~matplotlib.axes.Axes.set_xlabel` + +from matplotlib.font_manager import FontProperties + +font = FontProperties() +font.set_family('serif') +font.set_name('Times New Roman') +font.set_style('italic') + +fig, ax = plt.subplots(figsize=(5, 3)) +fig.subplots_adjust(bottom=0.15, left=0.2) +ax.plot(x1, y1) +ax.set_xlabel('time [s]', fontsize='large', fontweight='bold') +ax.set_ylabel('Damped oscillation [V]', fontproperties=font) + +plt.show() + +############################################################################## +# Finally, we can use native TeX rendering in all text objects and have +# multiple lines: + +fig, ax = plt.subplots(figsize=(5, 3)) +fig.subplots_adjust(bottom=0.2, left=0.2) +ax.plot(x1, np.cumsum(y1**2)) +ax.set_xlabel('time [s] \n This was a long experiment') +ax.set_ylabel(r'$\int\ Y^2\ dt\ \ [V^2 s]$') +plt.show() + + +############################################################################## +# Titles +# ====== +# +# Subplot titles are set in much the same way as labels, but there is +# the *loc* keyword arguments that can change the position and justification +# from the default value of ``loc=center``. + +fig, axs = plt.subplots(3, 1, figsize=(5, 6), tight_layout=True) +locs = ['center', 'left', 'right'] +for ax, loc in zip(axs, locs): + ax.plot(x1, y1) + ax.set_title('Title with loc at '+loc, loc=loc) +plt.show() + +############################################################################## +# Vertical spacing for titles is controlled via ``rcParams[axes.titlepad]``, +# which defaults to 5 points. Setting to a different value moves the title. + +fig, ax = plt.subplots(figsize=(5, 3)) +fig.subplots_adjust(top=0.8) +ax.plot(x1, y1) +ax.set_title('Vertically offset title', pad=30) +plt.show() + + +############################################################################## +# Ticks and ticklabels +# ==================== +# +# Placing ticks and ticklabels is a very tricky aspect of making a figure. +# Matplotlib does the best it can automatically, but it also offers a very +# flexible framework for determining the choices for tick locations, and +# how they are labelled. +# +# Terminology +# ~~~~~~~~~~~ +# +# *Axes* have an `matplotlib.axis` object for the ``ax.xaxis`` +# and ``ax.yaxis`` that +# contain the information about how the labels in the axis are laid out. +# +# The axis API is explained in detail in the documentation to +# `~matplotlib.axis`. +# +# An Axis object has major and minor ticks. The Axis has a +# `matplotlib.xaxis.set_major_locator` and +# `matplotlib.xaxis.set_minor_locator` methods that use the data being plotted +# to determine +# the location of major and minor ticks. There are also +# `matplotlib.xaxis.set_major_formatter` and +# `matplotlib.xaxis.set_minor_formatters` methods that format the tick labels. +# +# Simple ticks +# ~~~~~~~~~~~~ +# +# It often is convenient to simply define the +# tick values, and sometimes the tick labels, overriding the default +# locators and formatters. This is discouraged because it breaks itneractive +# navigation of the plot. It also can reset the axis limits: note that +# the second plot has the ticks we asked for, including ones that are +# well outside the automatic view limits. + +fig, axs = plt.subplots(2, 1, figsize=(5, 3), tight_layout=True) +axs[0].plot(x1, y1) +axs[1].plot(x1, y1) +axs[1].xaxis.set_ticks(np.arange(0., 8.1, 2.)) +plt.show() + +############################################################################# +# We can of course fix this after the fact, but it does highlight a +# weakness of hard-coding the ticks. This example also changes the format +# of the ticks: + +fig, axs = plt.subplots(2, 1, figsize=(5, 3), tight_layout=True) +axs[0].plot(x1, y1) +axs[1].plot(x1, y1) +ticks = np.arange(0., 8.1, 2.) +# list comprehension to get all tick labels... +tickla = ['%1.2f' % tick for tick in ticks] +axs[1].xaxis.set_ticks(ticks) +axs[1].xaxis.set_ticklabels(tickla) +axs[1].set_xlim(axs[0].get_xlim()) +plt.show() + +############################################################################# +# Tick Locators and Formatters +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Instead of making a list of all the tickalbels, we could have +# used a `matplotlib.ticker.FormatStrFormatter` and passed it to the +# ``ax.xaxis`` + +fig, axs = plt.subplots(2, 1, figsize=(5, 3), tight_layout=True) +axs[0].plot(x1, y1) +axs[1].plot(x1, y1) +ticks = np.arange(0., 8.1, 2.) +# list comprehension to get all tick labels... +formatter = matplotlib.ticker.StrMethodFormatter('{x:1.1f}') +axs[1].xaxis.set_ticks(ticks) +axs[1].xaxis.set_major_formatter(formatter) +axs[1].set_xlim(axs[0].get_xlim()) +plt.show() + +############################################################################# +# And of course we could have used a non-default locator to set the +# tick locations. Note we still pass in the tick values, but the +# x-limit fix used above is *not* needed. + +fig, axs = plt.subplots(2, 1, figsize=(5, 3), tight_layout=True) +axs[0].plot(x1, y1) +axs[1].plot(x1, y1) +formatter = matplotlib.ticker.FormatStrFormatter('%1.1f') +locator = matplotlib.ticker.FixedLocator(ticks) +axs[1].xaxis.set_major_locator(locator) +axs[1].xaxis.set_major_formatter(formatter) +plt.show() + +############################################################################# +# The default formatter is the `matplotlib.ticker.MaxNLocator` called as +# ``ticker.MaxNLocator(self, nbins='auto', steps=[1, 2, 2.5, 5, 10])`` +# The *steps* keyword contains a list of multiples that can be used for +# tick values. i.e. in this case, 2, 4, 6 would be acceptable ticks, +# as would 20, 40, 60 or 0.2, 0.4, 0.6. However, 3, 6, 9 would not be +# acceptable because 3 doesn't appear in the list of steps. +# +# ``nbins=auto`` uses an algorithm to determine how many ticks will +# be acceptable based on how long the axis is. The fontsize of the +# ticklabel is taken into account, but the length of the tick string +# is not (because its not yet known.) In the bottom row, the +# ticklabels are quite large, so we set ``nbins=4`` to make the +# labels fit in the right-hand plot. + +fig, axs = plt.subplots(2, 2, figsize=(8, 5), tight_layout=True) +axs = axs.flatten() +for n, ax in enumerate(axs): + ax.plot(x1*10., y1) + +formatter = matplotlib.ticker.FormatStrFormatter('%1.1f') +locator = matplotlib.ticker.MaxNLocator(nbins='auto', steps=[1, 4, 10]) +axs[1].xaxis.set_major_locator(locator) +axs[1].xaxis.set_major_formatter(formatter) + +formatter = matplotlib.ticker.FormatStrFormatter('%1.5f') +locator = matplotlib.ticker.AutoLocator() +axs[2].xaxis.set_major_formatter(formatter) +axs[2].xaxis.set_major_locator(locator) + +formatter = matplotlib.ticker.FormatStrFormatter('%1.5f') +locator = matplotlib.ticker.MaxNLocator(nbins=4) +axs[3].xaxis.set_major_formatter(formatter) +axs[3].xaxis.set_major_locator(locator) + +plt.show() + +############################################################################## +# Finally, we can specify functions for the formatter using +# `matplotlib.ticker.FuncFormatter`. + + +def formatoddticks(x, pos): + """Format odd tick positions + """ + if x % 2: + return '%1.2f' % x + else: + return '' + +fig, ax = plt.subplots(figsize=(5, 3), tight_layout=True) +ax.plot(x1, y1) +formatter = matplotlib.ticker.FuncFormatter(formatoddticks) +locator = matplotlib.ticker.MaxNLocator(nbins=6) +ax.xaxis.set_major_formatter(formatter) +ax.xaxis.set_major_locator(locator) + +plt.show() + +############################################################################## +# Dateticks +# ~~~~~~~~~ +# +# Matplotlib can accept `datetime.datetime` and `numpy.datetime64` +# objects as plotting arguments. Dates and times require special +# formatting, which can often benefit from manual intervention. In +# order to help, dates have spectial Locators and Formatters, +# defined in the `matplotlib.dates` module. +# +# A simple example is as follows. Note how we have to rotate the +# tick labels so that they don't over-run each other. +import datetime + +fig, ax = plt.subplots(figsize=(5, 3), tight_layout=True) +base = datetime.datetime(2017, 1, 1, 0, 0, 1) +time = [base + datetime.timedelta(days=x) for x in range(len(y1))] + +ax.plot(time, y1) +ax.tick_params(axis='x', rotation=70) +plt.show() + +############################################################################## +# Maybe the format of the labels above is acceptable, but the choices is +# rather idiosyncratic. We can make the ticks fall on the start of the month +# by modifying `matplotlib.dates.AutoDateLocator` +import matplotlib.dates as mdates + +locator = mdates.AutoDateLocator(interval_multiples=True) + +fig, ax = plt.subplots(figsize=(5, 3), tight_layout=True) +ax.xaxis.set_major_locator(locator) +ax.plot(time, y1) +ax.tick_params(axis='x', rotation=70) +plt.show() + +############################################################################## +# However, this changes the tick labels. The easiest fix is to pass a format +# to `matplotlib.dates.DateFormatter`. Also note that the 29th and the +# next month are very close together. We can fix this by using the +# `dates.DayLocator` class, which allows us to specify a list of days of the +# month to use. Similar formatters are listed in the `matplotlib.dates` module. + +locator = mdates.DayLocator(bymonthday=[1, 15]) +formatter = mdates.DateFormatter('%b %d') + +fig, ax = plt.subplots(figsize=(5, 3), tight_layout=True) +ax.xaxis.set_major_locator(locator) +ax.xaxis.set_major_formatter(formatter) +ax.plot(time, y1) +ax.tick_params(axis='x', rotation=70) +plt.show() + +############################################################################## +# Legends and Annotations +# ======================= +# +# - Legends: :doc:`/tutorials/intermediate/legend_guide` +# - Annotations: :doc:`/tutorials/text/annotations` +#