diff --git a/galleries/examples/scales/custom_scale.py b/galleries/examples/scales/custom_scale.py index 3a7f34fd22f0..0023bbcfbcae 100644 --- a/galleries/examples/scales/custom_scale.py +++ b/galleries/examples/scales/custom_scale.py @@ -1,4 +1,6 @@ """ +.. _custom_scale: + ============ Custom scale ============ diff --git a/galleries/users_explain/artists/artist_intro.rst b/galleries/users_explain/artists/artist_intro.rst index 5f82c972ca00..213a945f44b8 100644 --- a/galleries/users_explain/artists/artist_intro.rst +++ b/galleries/users_explain/artists/artist_intro.rst @@ -5,9 +5,8 @@ Introduction to Artists Almost all objects you interact with on a Matplotlib plot are called "Artist" (and are subclasses of the `.Artist` class). :doc:`Figure <../figure/index>` -and :doc:`Axes <../axes/index>` are Artists, and generally contain :doc:`Axis -<../axis/index>` Artists and Artists that contain data or annotation -information. +and :doc:`Axes <../axes/index>` are Artists, and generally contain +`~.axis.Axis` Artists and Artists that contain data or annotation information. Creating Artists diff --git a/galleries/users_explain/artists/index.rst b/galleries/users_explain/artists/index.rst index 26164d51f3a3..d3f2918c9a91 100644 --- a/galleries/users_explain/artists/index.rst +++ b/galleries/users_explain/artists/index.rst @@ -4,9 +4,8 @@ Artists Almost all objects you interact with on a Matplotlib plot are called "Artist" (and are subclasses of the `.Artist` class). :doc:`Figure <../figure/index>` -and :doc:`Axes <../axes/index>` are Artists, and generally contain :doc:`Axis -<../axis/index>` Artists and Artists that contain data or annotation -information. +and :doc:`Axes <../axes/index>` are Artists, and generally contain +`~.axis.Axis` Artists and Artists that contain data or annotation information. .. toctree:: :maxdepth: 2 diff --git a/galleries/users_explain/axes/axes_intro.rst b/galleries/users_explain/axes/axes_intro.rst index 17f9059b80af..948dd51c8b1e 100644 --- a/galleries/users_explain/axes/axes_intro.rst +++ b/galleries/users_explain/axes/axes_intro.rst @@ -5,7 +5,7 @@ Introduction to Axes (or Subplots) Matplotlib `~.axes.Axes` are the gateway to creating your data visualizations. Once an Axes is placed on a figure there are many methods that can be used to -add data to the Axes. An Axes typically has a pair of :doc:`Axis <../axis/index>` +add data to the Axes. An Axes typically has a pair of `~.axis.Axis` Artists that define the data coordinate system, and include methods to add annotations like x- and y-labels, titles, and legends. @@ -130,7 +130,7 @@ Note that text can also be added to axes using `~.axes.Axes.text`, and `~.axes.A Axes limits, scales, and ticking -------------------------------- -Each Axes has two (or more) `~.axis.Axis` objects, that can be accessed via :attr:`~matplotlib.axes.Axes.xaxis` and :attr:`~matplotlib.axes.Axes.yaxis` properties. These have substantial number of methods on them, and for highly customizable Axis-es it is useful to read more about that API (:doc:`../axis/index`). However, the Axes class offers a number of helpers for the most common of these methods. Indeed, the `~.axes.Axes.set_xlabel`, discussed above, is a helper for the `~.Axis.set_label_text`. +Each Axes has two (or more) `~.axis.Axis` objects, that can be accessed via :attr:`~matplotlib.axes.Axes.xaxis` and :attr:`~matplotlib.axes.Axes.yaxis` properties. These have substantial number of methods on them, and for highly customizable Axis-es it is useful to read the API at `~.axis.Axis`. However, the Axes class offers a number of helpers for the most common of these methods. Indeed, the `~.axes.Axes.set_xlabel`, discussed above, is a helper for the `~.Axis.set_label_text`. Other important methods set the extent on the axes (`~.axes.Axes.set_xlim`, `~.axes.Axes.set_ylim`), or more fundamentally the scale of the axes. So for instance, we can make an Axis have a logarithmic scale, and zoom in on a sub-portion of the data: @@ -158,7 +158,7 @@ Many aspects of Axes ticks and tick labeling can be adjusted using `~.axes.Axes. labelcolor='green') -More fine-grained control on ticks, setting scales, and controlling the Axis can be highly customized beyond these Axes-level helpers. An introduction to these methods can be found in :ref:`users_axis`, or the API reference for `.axis.Axis`. +More fine-grained control on ticks, setting scales, and controlling the Axis can be highly customized beyond these Axes-level helpers. Axes layout ----------- diff --git a/galleries/users_explain/axes/axes_scales.py b/galleries/users_explain/axes/axes_scales.py new file mode 100644 index 000000000000..567f3c5762ed --- /dev/null +++ b/galleries/users_explain/axes/axes_scales.py @@ -0,0 +1,223 @@ +""" +.. _user_axes_scales: + +=========== +Axis scales +=========== + +By default Matplotlib displays data on the axis using a linear scale. +Matplotlib also supports `logarithmic scales +`_, and other less common +scales as well. Usually this can be done directly by using the +`~.axes.Axes.set_xscale` or `~.axes.Axes.set_yscale` methods. + +""" +import matplotlib.pyplot as plt +import numpy as np + +import matplotlib.scale as mscale +from matplotlib.ticker import FixedLocator, NullFormatter + +fig, axs = plt.subplot_mosaic([['linear', 'linear-log'], + ['log-linear', 'log-log']], layout='constrained') + +x = np.arange(0, 3*np.pi, 0.1) +y = 2 * np.sin(x) + 3 + +ax = axs['linear'] +ax.plot(x, y) +ax.set_xlabel('linear') +ax.set_ylabel('linear') + +ax = axs['linear-log'] +ax.plot(x, y) +ax.set_yscale('log') +ax.set_xlabel('linear') +ax.set_ylabel('log') + +ax = axs['log-linear'] +ax.plot(x, y) +ax.set_xscale('log') +ax.set_xlabel('log') +ax.set_ylabel('linear') + +ax = axs['log-log'] +ax.plot(x, y) +ax.set_xscale('log') +ax.set_yscale('log') +ax.set_xlabel('log') +ax.set_ylabel('log') + +# %% +# loglog and semilogx/y +# ===================== +# +# The logarithmic axis is used so often that there are a set +# helper functions, that do the same thing: `~.axes.Axes.semilogy`, +# `~.axes.Axes.semilogx`, and `~.axes.Axes.loglog`. + +fig, axs = plt.subplot_mosaic([['linear', 'linear-log'], + ['log-linear', 'log-log']], layout='constrained') + +x = np.arange(0, 3*np.pi, 0.1) +y = 2 * np.sin(x) + 3 + +ax = axs['linear'] +ax.plot(x, y) +ax.set_xlabel('linear') +ax.set_ylabel('linear') +ax.set_title('plot(x, y)') + +ax = axs['linear-log'] +ax.semilogy(x, y) +ax.set_xlabel('linear') +ax.set_ylabel('log') +ax.set_title('semilogy(x, y)') + +ax = axs['log-linear'] +ax.semilogx(x, y) +ax.set_xlabel('log') +ax.set_ylabel('linear') +ax.set_title('semilogx(x, y)') + +ax = axs['log-log'] +ax.loglog(x, y) +ax.set_xlabel('log') +ax.set_ylabel('log') +ax.set_title('loglog(x, y)') + +# %% +# Other built-in scales +# ===================== +# +# There are other scales that can be used. The list of registered +# scales can be returned from `.scale.get_scale_names`: + +print(mscale.get_scale_names()) + +# %% +# + +todo = ['asinh', 'symlog', 'log', 'logit', ] +fig, axs = plt.subplot_mosaic([['asinh', 'symlog'], + ['log', 'logit']], layout='constrained') + +x = np.arange(0, 1000) + +for td in todo: + ax = axs[td] + if td in ['asinh', 'symlog']: + yy = x - np.mean(x) + elif td in ['logit']: + yy = (x-np.min(x)) + yy = yy / np.max(np.abs(yy)) + else: + yy = x + + ax.plot(yy, yy) + ax.set_yscale(td) + ax.set_title(td) + +# %% +# Optional arguments for scales +# ============================= +# +# Some of the default scales have optional arguments. These are +# documented in the API reference for the respective scales at +# `~.matplotlib.scale`. One can change the base of the logarithm +# being plotted (eg 2 below) or the linear threshold range +# for ``'symlog'``. + +fig, axs = plt.subplot_mosaic([['log', 'symlog']], layout='constrained', + figsize=(6.4, 3)) + +for td in axs: + ax = axs[td] + if td in ['log']: + ax.plot(x, x) + ax.set_yscale('log', base=2) + ax.set_title('log base=2') + else: + ax.plot(x - np.mean(x), x - np.mean(x)) + ax.set_yscale('symlog', linthresh=100) + ax.set_title('symlog linthresh=100') + + +# %% +# +# Arbitrary function scales +# ============================ +# +# Users can define a full scale class and pass that to `~.axes.Axes.set_xscale` +# and `~.axes.Axes.set_yscale` (see :ref:`custom_scale`). A short cut for this +# is to use the 'function' scale, and pass as extra arguments a ``forward`` and +# an ``inverse`` function. The following performs a `Mercator transform +# `_ to the y-axis. + +# Function Mercator transform +def forward(a): + a = np.deg2rad(a) + return np.rad2deg(np.log(np.abs(np.tan(a) + 1.0 / np.cos(a)))) + + +def inverse(a): + a = np.deg2rad(a) + return np.rad2deg(np.arctan(np.sinh(a))) + + +t = np.arange(0, 170.0, 0.1) +s = t / 2. + +fig, ax = plt.subplots(layout='constrained') +ax.plot(t, s, '-', lw=2) + +ax.set_yscale('function', functions=(forward, inverse)) +ax.set_title('function: Mercator') +ax.grid(True) +ax.set_xlim([0, 180]) +ax.yaxis.set_minor_formatter(NullFormatter()) +ax.yaxis.set_major_locator(FixedLocator(np.arange(0, 90, 10))) + + +# %% +# +# What is a "scale"? +# ================== +# +# A scale is an object that gets attached to an axis. The class documentation +# is at `~matplotlib.scale`. `~.axes.Axes.set_xscale` and `~.axes.Axes.set_yscale` +# set the scale on the respective Axis objects. You can determine the scale +# on an axis with `~.axis.Axis.get_scale`: + +fig, ax = plt.subplots(layout='constrained', + figsize=(3.2, 3)) +ax.semilogy(x, x) + +print(ax.xaxis.get_scale()) +print(ax.yaxis.get_scale()) + +# %% +# +# Setting a scale does three things. First it defines a transform on the axis +# that maps between data values to position along the axis. This transform can +# be accessed via ``get_transform``: + +print(ax.yaxis.get_transform()) + +# %% +# +# Transforms on the axis are a relatively low-level concept, but is one of the +# important roles played by ``set_scale``. +# +# Setting the scale also sets default tick locators (`~.ticker`) and tick +# formatters appropriate for the scale. An axis with a 'log' scale has a +# `~.ticker.LogLocator` to pick ticks at decade intervals, and a +# `~.ticker.LogFormatter` to use scientific notation on the decades. + +print('X axis') +print(ax.xaxis.get_major_locator()) +print(ax.xaxis.get_major_formatter()) + +print('Y axis') +print(ax.yaxis.get_major_locator()) +print(ax.yaxis.get_major_formatter()) diff --git a/galleries/users_explain/axes/axes_ticks.py b/galleries/users_explain/axes/axes_ticks.py new file mode 100644 index 000000000000..aaec87c6a239 --- /dev/null +++ b/galleries/users_explain/axes/axes_ticks.py @@ -0,0 +1,275 @@ +""" +.. _user_axes_ticks: + +========== +Axis Ticks +========== + +The x and y Axis on each Axes have default tick "locators" and "formatters" +that depend on the scale being used (see :ref:`user_axes_scales`). It is +possible to customize the ticks and tick labels with either high-level methods +like `~.axes.Axes.set_xticks` or set the locators and formatters directly on +the axis. + +Manual location and formats +=========================== + +The simplest method to customize the tick locations and formats is to use +`~.axes.Axes.set_xticks` and `~.axes.Axes.set_yticks`. These can be used on +either the major or the minor ticks. +""" +import numpy as np +import matplotlib.pyplot as plt + +import matplotlib.ticker as ticker + + +fig, axs = plt.subplots(2, 1, figsize=(5.4, 5.4), layout='constrained') +x = np.arange(100) +for nn, ax in enumerate(axs): + ax.plot(x, x) + if nn == 1: + ax.set_title('Manual ticks') + ax.set_yticks(np.arange(0, 100.1, 100/3)) + xticks = np.arange(0.50, 101, 20) + xlabels = [f'\\${x:1.2f}' for x in xticks] + ax.set_xticks(xticks, labels=xlabels) + else: + ax.set_title('Automatic ticks') + +# %% +# +# Note that the length of the ``labels`` argument must have the same length as +# the array used to specify the ticks. +# +# By default `~.axes.Axes.set_xticks` and `~.axes.Axes.set_yticks` act on the +# major ticks of an Axis, however it is possible to add minor ticks: + +fig, axs = plt.subplots(2, 1, figsize=(5.4, 5.4), layout='constrained') +x = np.arange(100) +for nn, ax in enumerate(axs): + ax.plot(x, x) + if nn == 1: + ax.set_title('Manual ticks') + ax.set_yticks(np.arange(0, 100.1, 100/3)) + ax.set_yticks(np.arange(0, 100.1, 100/30), minor=True) + else: + ax.set_title('Automatic ticks') + + +# %% +# +# Locators and Formatters +# ======================= +# +# Manually setting the ticks as above works well for specific final plots, but +# does not adapt as the user interacts with the axes. At a lower level, +# Matplotlib has ``Locators`` that are meant to automatically choose ticks +# depending on the current view limits of the axis, and ``Formatters`` that are +# meant to format the tick labels automatically. +# +# The full list of locators provided by Matplotlib are listed at +# :ref:`locators`, and the formatters at :ref:`formatters`. + + +# %% + +def setup(ax, title): + """Set up common parameters for the Axes in the example.""" + # only show the bottom spine + ax.yaxis.set_major_locator(ticker.NullLocator()) + ax.spines[['left', 'right', 'top']].set_visible(False) + + ax.xaxis.set_ticks_position('bottom') + ax.tick_params(which='major', width=1.00, length=5) + ax.tick_params(which='minor', width=0.75, length=2.5) + ax.set_xlim(0, 5) + ax.set_ylim(0, 1) + ax.text(0.0, 0.2, title, transform=ax.transAxes, + fontsize=14, fontname='Monospace', color='tab:blue') + + +fig, axs = plt.subplots(8, 1, layout='constrained') + +# Null Locator +setup(axs[0], title="NullLocator()") +axs[0].xaxis.set_major_locator(ticker.NullLocator()) +axs[0].xaxis.set_minor_locator(ticker.NullLocator()) + +# Multiple Locator +setup(axs[1], title="MultipleLocator(0.5)") +axs[1].xaxis.set_major_locator(ticker.MultipleLocator(0.5)) +axs[1].xaxis.set_minor_locator(ticker.MultipleLocator(0.1)) + +# Fixed Locator +setup(axs[2], title="FixedLocator([0, 1, 5])") +axs[2].xaxis.set_major_locator(ticker.FixedLocator([0, 1, 5])) +axs[2].xaxis.set_minor_locator(ticker.FixedLocator(np.linspace(0.2, 0.8, 4))) + +# Linear Locator +setup(axs[3], title="LinearLocator(numticks=3)") +axs[3].xaxis.set_major_locator(ticker.LinearLocator(3)) +axs[3].xaxis.set_minor_locator(ticker.LinearLocator(31)) + +# Index Locator +setup(axs[4], title="IndexLocator(base=0.5, offset=0.25)") +axs[4].plot(range(0, 5), [0]*5, color='white') +axs[4].xaxis.set_major_locator(ticker.IndexLocator(base=0.5, offset=0.25)) + +# Auto Locator +setup(axs[5], title="AutoLocator()") +axs[5].xaxis.set_major_locator(ticker.AutoLocator()) +axs[5].xaxis.set_minor_locator(ticker.AutoMinorLocator()) + +# MaxN Locator +setup(axs[6], title="MaxNLocator(n=4)") +axs[6].xaxis.set_major_locator(ticker.MaxNLocator(4)) +axs[6].xaxis.set_minor_locator(ticker.MaxNLocator(40)) + +# Log Locator +setup(axs[7], title="LogLocator(base=10, numticks=15)") +axs[7].set_xlim(10**3, 10**10) +axs[7].set_xscale('log') +axs[7].xaxis.set_major_locator(ticker.LogLocator(base=10, numticks=15)) +plt.show() + +# %% +# +# Similarly, we can specify "Formatters" for the major and minor ticks on each +# axis. +# +# The tick format is configured via the function `~.Axis.set_major_formatter` +# or `~.Axis.set_minor_formatter`. It accepts: +# +# - a format string, which implicitly creates a `.StrMethodFormatter`. +# - a function, implicitly creates a `.FuncFormatter`. +# - an instance of a `.Formatter` subclass. The most common are +# +# - `.NullFormatter`: No labels on the ticks. +# - `.StrMethodFormatter`: Use string `str.format` method. +# - `.FormatStrFormatter`: Use %-style formatting. +# - `.FuncFormatter`: Define labels through a function. +# - `.FixedFormatter`: Set the label strings explicitly. +# - `.ScalarFormatter`: Default formatter for scalars: auto-pick the format string. +# - `.PercentFormatter`: Format labels as a percentage. +# +# See :ref:`formatters` for the complete list. + + +def setup(ax, title): + """Set up common parameters for the Axes in the example.""" + # only show the bottom spine + ax.yaxis.set_major_locator(ticker.NullLocator()) + ax.spines[['left', 'right', 'top']].set_visible(False) + + # define tick positions + ax.xaxis.set_major_locator(ticker.MultipleLocator(1.00)) + ax.xaxis.set_minor_locator(ticker.MultipleLocator(0.25)) + + ax.xaxis.set_ticks_position('bottom') + ax.tick_params(which='major', width=1.00, length=5) + ax.tick_params(which='minor', width=0.75, length=2.5, labelsize=10) + ax.set_xlim(0, 5) + ax.set_ylim(0, 1) + ax.text(0.0, 0.2, title, transform=ax.transAxes, + fontsize=14, fontname='Monospace', color='tab:blue') + + +fig = plt.figure(figsize=(8, 8), layout='constrained') +fig0, fig1, fig2 = fig.subfigures(3, height_ratios=[1.5, 1.5, 7.5]) + +fig0.suptitle('String Formatting', fontsize=16, x=0, ha='left') +ax0 = fig0.subplots() + +setup(ax0, title="'{x} km'") +ax0.xaxis.set_major_formatter('{x} km') + +fig1.suptitle('Function Formatting', fontsize=16, x=0, ha='left') +ax1 = fig1.subplots() + +setup(ax1, title="def(x, pos): return str(x-5)") +ax1.xaxis.set_major_formatter(lambda x, pos: str(x-5)) + +fig2.suptitle('Formatter Object Formatting', fontsize=16, x=0, ha='left') +axs2 = fig2.subplots(7, 1) + +setup(axs2[0], title="NullFormatter()") +axs2[0].xaxis.set_major_formatter(ticker.NullFormatter()) + +setup(axs2[1], title="StrMethodFormatter('{x:.3f}')") +axs2[1].xaxis.set_major_formatter(ticker.StrMethodFormatter("{x:.3f}")) + +setup(axs2[2], title="FormatStrFormatter('#%d')") +axs2[2].xaxis.set_major_formatter(ticker.FormatStrFormatter("#%d")) + + +def fmt_two_digits(x, pos): + return f'[{x:.2f}]' + + +setup(axs2[3], title='FuncFormatter("[{:.2f}]".format)') +axs2[3].xaxis.set_major_formatter(ticker.FuncFormatter(fmt_two_digits)) + +setup(axs2[4], title="FixedFormatter(['A', 'B', 'C', 'D', 'E', 'F'])") +# FixedFormatter should only be used together with FixedLocator. +# Otherwise, one cannot be sure where the labels will end up. +positions = [0, 1, 2, 3, 4, 5] +labels = ['A', 'B', 'C', 'D', 'E', 'F'] +axs2[4].xaxis.set_major_locator(ticker.FixedLocator(positions)) +axs2[4].xaxis.set_major_formatter(ticker.FixedFormatter(labels)) + +setup(axs2[5], title="ScalarFormatter()") +axs2[5].xaxis.set_major_formatter(ticker.ScalarFormatter(useMathText=True)) + +setup(axs2[6], title="PercentFormatter(xmax=5)") +axs2[6].xaxis.set_major_formatter(ticker.PercentFormatter(xmax=5)) + + +# %% +# +# Styling ticks (tick parameters) +# =============================== +# +# The appearance of ticks can be controlled at a low level by finding the +# individual `~.axis.Tick` on the axis. However, usually it is simplest to +# use `~.axes.Axes.tick_params` to change all the objects at once. +# +# The ``tick_params`` method can change the properties of ticks: +# +# - length +# - direction (in or out of the frame) +# - colors +# - width and length +# - and whether the ticks are drawn at the bottom, top, left, or right of the +# Axes. +# +# It also can control the tick labels: +# +# - labelsize (fontsize) +# - labelcolor (color of the label) +# - labelrotation +# - labelbottom, labeltop, labelleft, labelright +# +# In addition there is a *pad* keyword argument that specifies how far the tick +# label is from the tick. +# +# Finally, the grid linestyles can be set: +# +# - grid_color +# - grid_alpha +# - grid_linewidth +# - grid_linestyle +# +# All these properties can be restricted to one axis, and can be applied to +# just the major or minor ticks + +fig, axs = plt.subplots(1, 2, figsize=(6.4, 3.2), layout='constrained') + +for nn, ax in enumerate(axs): + ax.plot(np.arange(100)) + if nn == 1: + ax.grid('on') + ax.tick_params(right=True, left=False, axis='y', color='r', length=16, + grid_color='none') + ax.tick_params(axis='x', color='m', length=4, direction='in', width=4, + labelcolor='g', grid_color='b') diff --git a/galleries/users_explain/axes/index.rst b/galleries/users_explain/axes/index.rst index a4df627c0671..3d0a67ca14e7 100644 --- a/galleries/users_explain/axes/index.rst +++ b/galleries/users_explain/axes/index.rst @@ -4,7 +4,7 @@ Axes and subplots Matplotlib `~.axes.Axes` are the gateway to creating your data visualizations. Once an Axes is placed on a figure there are many methods that can be used to -add data to the Axes. An Axes typically has a pair of :doc:`Axis <../axis/index>` +add data to the Axes. An Axes typically has a pair of `~.axis.Axis` Artists that define the data coordinate system, and include methods to add annotations like x- and y-labels, titles, and legends. @@ -36,7 +36,19 @@ annotations like x- and y-labels, titles, and legends. arranging_axes colorbar_placement Autoscaling axes + +.. toctree:: + :maxdepth: 2 + :includehidden: + + axes_scales + axes_ticks Legends Subplot mosaic + +.. toctree:: + :maxdepth: 1 + :includehidden: + Constrained layout guide Tight layout guide (mildly discouraged) diff --git a/galleries/users_explain/axis/index.rst b/galleries/users_explain/axis/index.rst deleted file mode 100644 index e6b8b81c8018..000000000000 --- a/galleries/users_explain/axis/index.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _users_axis: - -Controlling and labelling Axis objects --------------------------------------- - -Some good material in artist tutorial that should be cribbed from or used here. diff --git a/galleries/users_explain/index.rst b/galleries/users_explain/index.rst index 16ac05f06671..8ed30a9aad8d 100644 --- a/galleries/users_explain/index.rst +++ b/galleries/users_explain/index.rst @@ -53,15 +53,6 @@ Using Matplotlib customizing - .. grid-item-card:: - :padding: 2 - - .. toctree:: - :maxdepth: 2 - :includehidden: - - axis/index - .. grid-item-card:: :padding: 2 diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 3e0b5c926208..ca6923d92a38 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -774,6 +774,7 @@ def set_label_coords(self, x, y, transform=None): self.stale = True def get_transform(self): + """Return the transform used in the Axis' scale""" return self._scale.get_transform() def get_scale(self): diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 3c49f3a07700..b4793929de9b 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -9,6 +9,9 @@ Although the locators know nothing about major or minor ticks, they are used by the Axis class to support major and minor tick locating and formatting. +.. _tick_locating: +.. _locators: + Tick locating -------------