From ac69b1035a42e47798e4ea7167f616754be49452 Mon Sep 17 00:00:00 2001 From: Todd Jennings Date: Wed, 4 Mar 2020 21:48:38 -0500 Subject: [PATCH] Automatically create tick formatters for str and callable inputs. --- .flake8 | 8 ++ .../2020-03-06-auto-tick-formatters.rst | 7 ++ examples/mplot3d/surface3d.py | 23 +++- examples/pyplots/dollar_ticks.py | 18 ++- examples/showcase/anatomy.py | 28 ++++- .../showcase/bachelors_degrees_by_gender.py | 24 +++- .../date_index_formatter.py | 21 +++- examples/ticks_and_spines/custom_ticker1.py | 29 +++-- examples/ticks_and_spines/major_minor_demo.py | 38 ++++-- examples/ticks_and_spines/tick-formatters.py | 112 +++++++++++++----- .../tick_labels_from_values.py | 29 ++++- lib/matplotlib/axis.py | 62 +++++++--- lib/matplotlib/tests/test_axes.py | 51 ++++++++ lib/matplotlib/ticker.py | 10 +- tutorials/intermediate/artists.py | 25 +--- tutorials/introductory/lifecycle.py | 38 +++--- tutorials/text/text_intro.py | 23 ++-- 17 files changed, 413 insertions(+), 133 deletions(-) create mode 100644 doc/users/next_whats_new/2020-03-06-auto-tick-formatters.rst diff --git a/.flake8 b/.flake8 index cd38366d0969..e01e27d2c957 100644 --- a/.flake8 +++ b/.flake8 @@ -180,6 +180,7 @@ per-file-ignores = examples/misc/svg_filter_line.py: E402 examples/misc/svg_filter_pie.py: E402 examples/misc/table_demo.py: E201 + examples/mplot3d/surface3d.py: E402 examples/pie_and_polar_charts/bar_of_pie.py: E402 examples/pie_and_polar_charts/nested_pie.py: E402 examples/pie_and_polar_charts/pie_and_donut_labels.py: E402 @@ -232,6 +233,8 @@ per-file-ignores = examples/shapes_and_collections/path_patch.py: E402 examples/shapes_and_collections/quad_bezier.py: E402 examples/shapes_and_collections/scatter.py: E402 + examples/showcase/anatomy.py: E402 + examples/showcase/bachelors_degrees_by_gender.py: E402 examples/showcase/firefox.py: E501 examples/specialty_plots/anscombe.py: E402 examples/specialty_plots/radar_chart.py: E402 @@ -251,6 +254,7 @@ per-file-ignores = examples/subplots_axes_and_figures/two_scales.py: E402 examples/subplots_axes_and_figures/zoom_inset_axes.py: E402 examples/tests/backend_driver_sgskip.py: E402 + examples/text_labels_and_annotations/date_index_formatter.py: E402 examples/text_labels_and_annotations/demo_text_rotation_mode.py: E402 examples/text_labels_and_annotations/custom_legends.py: E402 examples/text_labels_and_annotations/fancyarrow_demo.py: E402 @@ -261,7 +265,11 @@ per-file-ignores = examples/text_labels_and_annotations/mathtext_asarray.py: E402 examples/text_labels_and_annotations/tex_demo.py: E402 examples/text_labels_and_annotations/watermark_text.py: E402 + examples/ticks_and_spines/custom_ticker1.py: E402 examples/ticks_and_spines/date_concise_formatter.py: E402 + examples/ticks_and_spines/major_minor_demo.py: E402 + examples/ticks_and_spines/tick-formatters.py: E402 + examples/ticks_and_spines/tick_labels_from_values.py: E402 examples/user_interfaces/canvasagg.py: E402 examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py: E402 examples/user_interfaces/embedding_in_gtk3_sgskip.py: E402 diff --git a/doc/users/next_whats_new/2020-03-06-auto-tick-formatters.rst b/doc/users/next_whats_new/2020-03-06-auto-tick-formatters.rst new file mode 100644 index 000000000000..d6c526513d0c --- /dev/null +++ b/doc/users/next_whats_new/2020-03-06-auto-tick-formatters.rst @@ -0,0 +1,7 @@ +Allow tick formatters to be set with str or function inputs +------------------------------------------------------------------------ +`~.Axis.set_major_formatter` and `~.Axis.set_minor_formatter` +now accept `str` or function inputs in addition to `~.ticker.Formatter` +instances. For a `str` a `~.ticker.StrMethodFormatter` is automatically +generated and used. For a function a `~.ticker.FuncFormatter` is automatically +generated and used. diff --git a/examples/mplot3d/surface3d.py b/examples/mplot3d/surface3d.py index e71ae2c73302..51912ed4b3c4 100644 --- a/examples/mplot3d/surface3d.py +++ b/examples/mplot3d/surface3d.py @@ -12,7 +12,7 @@ import matplotlib.pyplot as plt from matplotlib import cm -from matplotlib.ticker import LinearLocator, FormatStrFormatter +from matplotlib.ticker import LinearLocator import numpy as np fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) @@ -31,9 +31,28 @@ # Customize the z axis. ax.set_zlim(-1.01, 1.01) ax.zaxis.set_major_locator(LinearLocator(10)) -ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f')) +# A StrMethodFormatter is used automatically +ax.zaxis.set_major_formatter('{x:.02f}') # Add a color bar which maps values to colors. fig.colorbar(surf, shrink=0.5, aspect=5) plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.subplots +matplotlib.axis.Axis.set_major_formatter +matplotlib.axis.Axis.set_major_locator +matplotlib.ticker.LinearLocator +matplotlib.ticker.StrMethodFormatter diff --git a/examples/pyplots/dollar_ticks.py b/examples/pyplots/dollar_ticks.py index 0ed367645577..fb7032c052a6 100644 --- a/examples/pyplots/dollar_ticks.py +++ b/examples/pyplots/dollar_ticks.py @@ -7,7 +7,6 @@ """ import numpy as np import matplotlib.pyplot as plt -import matplotlib.ticker as ticker # Fixing random state for reproducibility np.random.seed(19680801) @@ -15,16 +14,15 @@ fig, ax = plt.subplots() ax.plot(100*np.random.rand(20)) -formatter = ticker.FormatStrFormatter('$%1.2f') -ax.yaxis.set_major_formatter(formatter) +# Use automatic StrMethodFormatter +ax.yaxis.set_major_formatter('${x:1.2f}') -for tick in ax.yaxis.get_major_ticks(): - tick.label1.set_visible(False) - tick.label2.set_visible(True) - tick.label2.set_color('green') +ax.yaxis.set_tick_params(which='major', labelcolor='green', + labelleft=False, labelright=True) plt.show() + ############################################################################# # # ------------ @@ -36,8 +34,8 @@ # in this example: import matplotlib -matplotlib.ticker -matplotlib.ticker.FormatStrFormatter +matplotlib.pyplot.subplots matplotlib.axis.Axis.set_major_formatter -matplotlib.axis.Axis.get_major_ticks +matplotlib.axis.Axis.set_tick_params matplotlib.axis.Tick +matplotlib.ticker.StrMethodFormatter diff --git a/examples/showcase/anatomy.py b/examples/showcase/anatomy.py index bd1cee43627c..96afa7f5d408 100644 --- a/examples/showcase/anatomy.py +++ b/examples/showcase/anatomy.py @@ -8,7 +8,7 @@ import numpy as np import matplotlib.pyplot as plt -from matplotlib.ticker import AutoMinorLocator, MultipleLocator, FuncFormatter +from matplotlib.ticker import AutoMinorLocator, MultipleLocator np.random.seed(19680801) @@ -24,13 +24,14 @@ def minor_tick(x, pos): if not x % 1.0: return "" - return "%.2f" % x + return f"{x:.2f}" ax.xaxis.set_major_locator(MultipleLocator(1.000)) ax.xaxis.set_minor_locator(AutoMinorLocator(4)) ax.yaxis.set_major_locator(MultipleLocator(1.000)) ax.yaxis.set_minor_locator(AutoMinorLocator(4)) -ax.xaxis.set_minor_formatter(FuncFormatter(minor_tick)) +# FuncFormatter is created and used automatically +ax.xaxis.set_minor_formatter(minor_tick) ax.set_xlim(0, 4) ax.set_ylim(0, 4) @@ -141,3 +142,24 @@ def text(x, y, text): fontsize=10, ha="right", color='.5') plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.figure +matplotlib.axes.Axes.text +matplotlib.axis.Axis.set_minor_formatter +matplotlib.axis.Axis.set_major_locator +matplotlib.axis.Axis.set_minor_locator +matplotlib.patches.Circle +matplotlib.patheffects.withStroke +matplotlib.ticker.FuncFormatter diff --git a/examples/showcase/bachelors_degrees_by_gender.py b/examples/showcase/bachelors_degrees_by_gender.py index 3f4c805241c7..079fae651ead 100644 --- a/examples/showcase/bachelors_degrees_by_gender.py +++ b/examples/showcase/bachelors_degrees_by_gender.py @@ -51,8 +51,9 @@ # Set a fixed location and format for ticks. ax.set_xticks(range(1970, 2011, 10)) ax.set_yticks(range(0, 91, 10)) -ax.xaxis.set_major_formatter(plt.FuncFormatter('{:.0f}'.format)) -ax.yaxis.set_major_formatter(plt.FuncFormatter('{:.0f}%'.format)) +# Use automatic StrMethodFormatter creation +ax.xaxis.set_major_formatter('{x:.0f}') +ax.yaxis.set_major_formatter('{x:.0f}%') # Provide tick lines across the plot to help your viewers trace along # the axis ticks. Make sure that the lines are light and small so they @@ -113,3 +114,22 @@ # Just change the file extension in this call. # fig.savefig('percent-bachelors-degrees-women-usa.png', bbox_inches='tight') plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.subplots +matplotlib.axes.Axes.text +matplotlib.axis.Axis.set_major_formatter +matplotlib.axis.XAxis.tick_bottom +matplotlib.axis.YAxis.tick_left +matplotlib.artist.Artist.set_visible +matplotlib.ticker.StrMethodFormatter diff --git a/examples/text_labels_and_annotations/date_index_formatter.py b/examples/text_labels_and_annotations/date_index_formatter.py index 4d6b0dd49f34..a8f3f9e64847 100644 --- a/examples/text_labels_and_annotations/date_index_formatter.py +++ b/examples/text_labels_and_annotations/date_index_formatter.py @@ -10,7 +10,6 @@ import numpy as np import matplotlib.pyplot as plt import matplotlib.cbook as cbook -import matplotlib.ticker as ticker # Load a numpy record array from yahoo csv data with fields date, open, close, # volume, adj_close from the mpl-data/example directory. The record array @@ -36,8 +35,26 @@ def format_date(x, pos=None): ax2.plot(ind, r.adj_close, 'o-') -ax2.xaxis.set_major_formatter(ticker.FuncFormatter(format_date)) +# Use automatic FuncFormatter creation +ax2.xaxis.set_major_formatter(format_date) ax2.set_title("Custom tick formatter") fig.autofmt_xdate() plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.subplots +matplotlib.axis.Axis.set_major_formatter +matplotlib.cbook.get_sample_data +matplotlib.ticker.FuncFormatter diff --git a/examples/ticks_and_spines/custom_ticker1.py b/examples/ticks_and_spines/custom_ticker1.py index bc4188ec2b39..c45802efbc43 100644 --- a/examples/ticks_and_spines/custom_ticker1.py +++ b/examples/ticks_and_spines/custom_ticker1.py @@ -11,23 +11,32 @@ In this example a user defined function is used to format the ticks in millions of dollars on the y axis. """ -from matplotlib.ticker import FuncFormatter import matplotlib.pyplot as plt -import numpy as np -x = np.arange(4) money = [1.5e5, 2.5e6, 5.5e6, 2.0e7] def millions(x, pos): """The two args are the value and tick position.""" - return '$%1.1fM' % (x * 1e-6) - - -formatter = FuncFormatter(millions) + return '${:1.1f}M'.format(x*1e-6) fig, ax = plt.subplots() -ax.yaxis.set_major_formatter(formatter) -plt.bar(x, money) -plt.xticks(x, ('Bill', 'Fred', 'Mary', 'Sue')) +# Use automatic FuncFormatter creation +ax.yaxis.set_major_formatter(millions) +ax.bar(['Bill', 'Fred', 'Mary', 'Sue'], money) plt.show() + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.subplots +matplotlib.axis.Axis.set_major_formatter +matplotlib.ticker.FuncFormatter diff --git a/examples/ticks_and_spines/major_minor_demo.py b/examples/ticks_and_spines/major_minor_demo.py index d73e5fbc0acd..34bc773a839a 100644 --- a/examples/ticks_and_spines/major_minor_demo.py +++ b/examples/ticks_and_spines/major_minor_demo.py @@ -14,8 +14,12 @@ Minor tick labels can be turned on by setting the minor formatter. `.MultipleLocator` places ticks on multiples of some base. -`.FormatStrFormatter` uses a format string (e.g., ``'%d'`` or ``'%1.2f'`` or -``'%1.1f cm'``) to format the tick labels. +`.StrMethodFormatter` uses a format string (e.g., ``'{x:d}'`` or ``'{x:1.2f}'`` +or ``'{x:1.1f} cm'``) to format the tick labels (the variable in the format +string must be ``'x'``). For a `.StrMethodFormatter`, the string can be passed +directly to `.Axis.set_major_formatter` or +`.Axis.set_minor_formatter`. An appropriate `.StrMethodFormatter` will +be created and used automatically. `.pyplot.grid` changes the grid settings of the major ticks of the y and y axis together. If you want to control the grid of the minor ticks for a given axis, @@ -29,8 +33,7 @@ import matplotlib.pyplot as plt import numpy as np -from matplotlib.ticker import (MultipleLocator, FormatStrFormatter, - AutoMinorLocator) +from matplotlib.ticker import (MultipleLocator, AutoMinorLocator) t = np.arange(0.0, 100.0, 0.1) @@ -40,10 +43,11 @@ ax.plot(t, s) # Make a plot with major ticks that are multiples of 20 and minor ticks that -# are multiples of 5. Label major ticks with '%d' formatting but don't label -# minor ticks. +# are multiples of 5. Label major ticks with '.0f' formatting but don't label +# minor ticks. The string is used directly, the `StrMethodFormatter` is +# created automatically. ax.xaxis.set_major_locator(MultipleLocator(20)) -ax.xaxis.set_major_formatter(FormatStrFormatter('%d')) +ax.xaxis.set_major_formatter('{x:.0f}') # For the minor ticks, use no labels; default NullFormatter. ax.xaxis.set_minor_locator(MultipleLocator(5)) @@ -74,3 +78,23 @@ ax.tick_params(which='minor', length=4, color='r') plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.subplots +matplotlib.axis.Axis.set_major_formatter +matplotlib.axis.Axis.set_major_locator +matplotlib.axis.Axis.set_minor_locator +matplotlib.ticker.AutoMinorLocator +matplotlib.ticker.MultipleLocator +matplotlib.ticker.StrMethodFormatter diff --git a/examples/ticks_and_spines/tick-formatters.py b/examples/ticks_and_spines/tick-formatters.py index 000c2f6b74cc..4533ea74df0c 100644 --- a/examples/ticks_and_spines/tick-formatters.py +++ b/examples/ticks_and_spines/tick-formatters.py @@ -10,7 +10,7 @@ """ import matplotlib.pyplot as plt -import matplotlib.ticker as ticker +from matplotlib import ticker def setup(ax, title): @@ -34,46 +34,104 @@ def setup(ax, title): fontsize=14, fontname='Monospace', color='tab:blue') -fig, axs = plt.subplots(7, 1, figsize=(8, 6)) +# Tick formatters can be set in one of two ways, either by passing a ``str`` +# or function to `~.Axis.set_major_formatter` or `~.Axis.set_minor_formatter`, +# or by creating an instance of one of the various `~.ticker.Formatter` classes +# and providing that to `~.Axis.set_major_formatter` or +# `~.Axis.set_minor_formatter`. -# Null formatter -setup(axs[0], title="NullFormatter()") -axs[0].xaxis.set_major_formatter(ticker.NullFormatter()) +# The first two examples directly pass a ``str`` or function. -# Fixed formatter -setup(axs[1], title="FixedFormatter(['A', 'B', 'C', ...])") -# 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'] -axs[1].xaxis.set_major_locator(ticker.FixedLocator(positions)) -axs[1].xaxis.set_major_formatter(ticker.FixedFormatter(labels)) +fig0, axs0 = plt.subplots(2, 1, figsize=(8, 2)) +fig0.suptitle('Simple Formatting') + +# A ``str``, using format string function syntax, can be used directly as a +# formatter. The variable ``x`` is the tick value and the variable ``pos`` is +# tick position. This creates a StrMethodFormatter automatically. +setup(axs0[0], title="'{x} km'") +axs0[0].xaxis.set_major_formatter('{x} km') +# A function can also be used directly as a formatter. The function must take +# two arguments: ``x`` for the tick value and ``pos`` for the tick position, +# and must return a ``str`` This creates a FuncFormatter automatically. +setup(axs0[1], title="lambda x, pos: str(x-5)") +axs0[1].xaxis.set_major_formatter(lambda x, pos: str(x-5)) + +fig0.tight_layout() + + +# The remaining examples use Formatter objects. + +fig1, axs1 = plt.subplots(7, 1, figsize=(8, 6)) +fig1.suptitle('Formatter Object Formatting') + +# Null formatter +setup(axs1[0], title="NullFormatter()") +axs1[0].xaxis.set_major_formatter(ticker.NullFormatter()) + +# StrMethod formatter +setup(axs1[1], title="StrMethodFormatter('{x:.3f}')") +axs1[1].xaxis.set_major_formatter(ticker.StrMethodFormatter("{x:.3f}")) # FuncFormatter can be used as a decorator @ticker.FuncFormatter def major_formatter(x, pos): - return "[%.2f]" % x + return f'[{x:.2f}]' -setup(axs[2], title='FuncFormatter(lambda x, pos: "[%.2f]" % x)') -axs[2].xaxis.set_major_formatter(major_formatter) +setup(axs1[2], title='FuncFormatter("[{:.2f}]".format') +axs1[2].xaxis.set_major_formatter(major_formatter) -# FormatStr formatter -setup(axs[3], title="FormatStrFormatter('#%d')") -axs[3].xaxis.set_major_formatter(ticker.FormatStrFormatter("#%d")) +# Fixed formatter +setup(axs1[3], title="FixedFormatter(['A', 'B', 'C', ...])") +# 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'] +axs1[3].xaxis.set_major_locator(ticker.FixedLocator(positions)) +axs1[3].xaxis.set_major_formatter(ticker.FixedFormatter(labels)) # Scalar formatter -setup(axs[4], title="ScalarFormatter()") -axs[4].xaxis.set_major_formatter(ticker.ScalarFormatter(useMathText=True)) +setup(axs1[4], title="ScalarFormatter()") +axs1[4].xaxis.set_major_formatter(ticker.ScalarFormatter(useMathText=True)) -# StrMethod formatter -setup(axs[5], title="StrMethodFormatter('{x:.3f}')") -axs[5].xaxis.set_major_formatter(ticker.StrMethodFormatter("{x:.3f}")) +# FormatStr formatter +setup(axs1[5], title="FormatStrFormatter('#%d')") +axs1[5].xaxis.set_major_formatter(ticker.FormatStrFormatter("#%d")) # Percent formatter -setup(axs[6], title="PercentFormatter(xmax=5)") -axs[6].xaxis.set_major_formatter(ticker.PercentFormatter(xmax=5)) +setup(axs1[6], title="PercentFormatter(xmax=5)") +axs1[6].xaxis.set_major_formatter(ticker.PercentFormatter(xmax=5)) -plt.tight_layout() +fig1.tight_layout() plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.subplots +matplotlib.axes.Axes.text +matplotlib.axis.Axis.set_major_formatter +matplotlib.axis.Axis.set_major_locator +matplotlib.axis.Axis.set_minor_locator +matplotlib.axis.XAxis.set_ticks_position +matplotlib.axis.YAxis.set_ticks_position +matplotlib.ticker.FixedFormatter +matplotlib.ticker.FixedLocator +matplotlib.ticker.FormatStrFormatter +matplotlib.ticker.FuncFormatter +matplotlib.ticker.MultipleLocator +matplotlib.ticker.NullFormatter +matplotlib.ticker.NullLocator +matplotlib.ticker.PercentFormatter +matplotlib.ticker.ScalarFormatter +matplotlib.ticker.StrMethodFormatter diff --git a/examples/ticks_and_spines/tick_labels_from_values.py b/examples/ticks_and_spines/tick_labels_from_values.py index c504796b7a1b..355692d89890 100644 --- a/examples/ticks_and_spines/tick_labels_from_values.py +++ b/examples/ticks_and_spines/tick_labels_from_values.py @@ -3,20 +3,22 @@ Setting tick labels from a list of values ========================================= -Using ax.set_xticks causes the tick labels to be set on the currently +Using `.Axes.set_xticks` causes the tick labels to be set on the currently chosen ticks. However, you may want to allow matplotlib to dynamically choose the number of ticks and their spacing. In this case it may be better to determine the tick label from the value at the tick. The following example shows how to do this. -NB: The MaxNLocator is used here to ensure that the tick values +NB: The `.ticker.MaxNLocator` is used here to ensure that the tick values take integer values. """ import matplotlib.pyplot as plt -from matplotlib.ticker import FuncFormatter, MaxNLocator +from matplotlib.ticker import MaxNLocator + + fig, ax = plt.subplots() xs = range(26) ys = range(26) @@ -30,7 +32,26 @@ def format_fn(tick_val, tick_pos): return '' -ax.xaxis.set_major_formatter(FuncFormatter(format_fn)) +# A FuncFormatter is created automatically. +ax.xaxis.set_major_formatter(format_fn) ax.xaxis.set_major_locator(MaxNLocator(integer=True)) ax.plot(xs, ys) plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.pyplot.subplots +matplotlib.axis.Axis.set_major_formatter +matplotlib.axis.Axis.set_major_locator +matplotlib.ticker.FuncFormatter +matplotlib.ticker.MaxNLocator diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 30ab914faeaa..1295d351d0b8 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1541,37 +1541,65 @@ def set_major_formatter(self, formatter): """ Set the formatter of the major ticker. + In addition to a `~matplotlib.ticker.Formatter` instance, + this also accepts a ``str`` or function. + + For a ``str`` a `~matplotlib.ticker.StrMethodFormatter` is used. + The field used for the value must be labeled ``'x'`` and the field used + for the position must be labeled ``'pos'``. + See the `~matplotlib.ticker.StrMethodFormatter` documentation for + more information. + + For a function, a `~matplotlib.ticker.FuncFormatter` is used. + The function must take two inputs (a tick value ``x`` and a + position ``pos``), and return a string containing the corresponding + tick label. + See the `~matplotlib.ticker.FuncFormatter` documentation for + more information. + Parameters ---------- - formatter : `~matplotlib.ticker.Formatter` + formatter : `~matplotlib.ticker.Formatter`, ``str``, or function """ - cbook._check_isinstance(mticker.Formatter, formatter=formatter) - if (isinstance(formatter, mticker.FixedFormatter) - and len(formatter.seq) > 0 - and not isinstance(self.major.locator, mticker.FixedLocator)): - cbook._warn_external('FixedFormatter should only be used together ' - 'with FixedLocator') - self.isDefault_majfmt = False - self.major.formatter = formatter - formatter.set_axis(self) - self.stale = True + self._set_formatter(formatter, self.major) def set_minor_formatter(self, formatter): """ Set the formatter of the minor ticker. + In addition to a `~matplotlib.ticker.Formatter` instance, + this also accepts a ``str`` or function. + See `.Axis.set_major_formatter` for more information. + Parameters ---------- - formatter : `~matplotlib.ticker.Formatter` - """ - cbook._check_isinstance(mticker.Formatter, formatter=formatter) + formatter : `~matplotlib.ticker.Formatter`, ``str``, or function + """ + self._set_formatter(formatter, self.minor) + + def _set_formatter(self, formatter, level): + if isinstance(formatter, str): + formatter = mticker.StrMethodFormatter(formatter) + # Don't allow any other TickHelper to avoid easy-to-make errors, + # like using a Locator instead of a Formatter. + elif (callable(formatter) and + not isinstance(formatter, mticker.TickHelper)): + formatter = mticker.FuncFormatter(formatter) + else: + cbook._check_isinstance(mticker.Formatter, formatter=formatter) + if (isinstance(formatter, mticker.FixedFormatter) and len(formatter.seq) > 0 - and not isinstance(self.minor.locator, mticker.FixedLocator)): + and not isinstance(level.locator, mticker.FixedLocator)): cbook._warn_external('FixedFormatter should only be used together ' 'with FixedLocator') - self.isDefault_minfmt = False - self.minor.formatter = formatter + + if level == self.major: + self.isDefault_majfmt = False + else: + self.isDefault_minfmt = False + + level.formatter = formatter formatter.set_axis(self) self.stale = True diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 8d512b7ed920..8d6c7233ac25 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -205,6 +205,57 @@ def test_formatter_ticker(): ax.autoscale_view() +def test_funcformatter_auto_formatter(): + def _formfunc(x, pos): + return '' + + ax = plt.figure().subplots() + + assert ax.xaxis.isDefault_majfmt + assert ax.xaxis.isDefault_minfmt + assert ax.yaxis.isDefault_majfmt + assert ax.yaxis.isDefault_minfmt + + ax.xaxis.set_major_formatter(_formfunc) + + assert not ax.xaxis.isDefault_majfmt + assert ax.xaxis.isDefault_minfmt + assert ax.yaxis.isDefault_majfmt + assert ax.yaxis.isDefault_minfmt + + targ_funcformatter = mticker.FuncFormatter(_formfunc) + + assert isinstance(ax.xaxis.get_major_formatter(), + mticker.FuncFormatter) + + assert ax.xaxis.get_major_formatter().func == targ_funcformatter.func + + +def test_strmethodformatter_auto_formatter(): + formstr = '{x}_{pos}' + + ax = plt.figure().subplots() + + assert ax.xaxis.isDefault_majfmt + assert ax.xaxis.isDefault_minfmt + assert ax.yaxis.isDefault_majfmt + assert ax.yaxis.isDefault_minfmt + + ax.yaxis.set_minor_formatter(formstr) + + assert ax.xaxis.isDefault_majfmt + assert ax.xaxis.isDefault_minfmt + assert ax.yaxis.isDefault_majfmt + assert not ax.yaxis.isDefault_minfmt + + targ_strformatter = mticker.StrMethodFormatter(formstr) + + assert isinstance(ax.yaxis.get_minor_formatter(), + mticker.StrMethodFormatter) + + assert ax.yaxis.get_minor_formatter().fmt == targ_strformatter.fmt + + @image_comparison(["twin_axis_locators_formatters"]) def test_twin_axis_locators_formatters(): vals = np.linspace(0, 1, num=5, endpoint=True) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 277f55a1afab..678d612a72f4 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -159,6 +159,12 @@ ax.yaxis.set_major_formatter(ymajor_formatter) ax.yaxis.set_minor_formatter(yminor_formatter) +In addition to a `.Formatter` instance, `~.Axis.set_major_formatter` and +`~.Axis.set_minor_formatter` also accept a ``str`` or function. ``str`` input +will be internally replaced with an autogenerated `.StrMethodFormatter` with +the input ``str``. For function input, a `.FuncFormatter` with the input +function will be generated and used. + See :doc:`/gallery/ticks_and_spines/major_minor_demo` for an example of setting major and minor ticks. See the :mod:`matplotlib.dates` module for more information and examples of using date locators and formatters. @@ -416,8 +422,8 @@ class StrMethodFormatter(Formatter): """ Use a new-style format string (as used by `str.format`) to format the tick. - The field used for the value must be labeled *x* and the field used - for the position must be labeled *pos*. + The field used for the tick value must be labeled *x* and the field used + for the tick position must be labeled *pos*. """ def __init__(self, fmt): self.fmt = fmt diff --git a/tutorials/intermediate/artists.py b/tutorials/intermediate/artists.py index e136940c8320..ed13c91a5e09 100644 --- a/tutorials/intermediate/artists.py +++ b/tutorials/intermediate/artists.py @@ -650,22 +650,9 @@ class in the matplotlib API, and the one you will be working with most # ============== ========================================================== # # Here is an example which sets the formatter for the right side ticks with -# dollar signs and colors them green on the right side of the yaxis - -import matplotlib.ticker as ticker - -# Fixing random state for reproducibility -np.random.seed(19680801) - -fig, ax = plt.subplots() -ax.plot(100*np.random.rand(20)) - -formatter = ticker.FormatStrFormatter('$%1.2f') -ax.yaxis.set_major_formatter(formatter) - -for tick in ax.yaxis.get_major_ticks(): - tick.label1.set_visible(False) - tick.label2.set_visible(True) - tick.label2.set_color('green') - -plt.show() +# dollar signs and colors them green on the right side of the yaxis. +# +# +# .. include:: ../../gallery/pyplots/dollar_ticks.rst +# :start-after: y axis labels. +# :end-before: ------- diff --git a/tutorials/introductory/lifecycle.py b/tutorials/introductory/lifecycle.py index 3ef879188c89..3b3a4cc2d9a4 100644 --- a/tutorials/introductory/lifecycle.py +++ b/tutorials/introductory/lifecycle.py @@ -5,7 +5,7 @@ This tutorial aims to show the beginning, middle, and end of a single visualization using Matplotlib. We'll begin with some raw data and -end by saving a figure of a customized visualization. Along the way we'll try +end by saving a figure of a customized visualization. Along the way we try to highlight some neat features and best-practices using Matplotlib. .. currentmodule:: matplotlib @@ -53,7 +53,7 @@ # sphinx_gallery_thumbnail_number = 10 import numpy as np import matplotlib.pyplot as plt -from matplotlib.ticker import FuncFormatter + data = {'Barton LLC': 109438.50, 'Frami, Hills and Schmidt': 103569.59, @@ -74,7 +74,7 @@ # =============== # # This data is naturally visualized as a barplot, with one bar per -# group. To do this with the object-oriented approach, we'll first generate +# group. To do this with the object-oriented approach, we first generate # an instance of :class:`figure.Figure` and # :class:`axes.Axes`. The Figure is like a canvas, and the Axes # is a part of that canvas on which we will make a particular visualization. @@ -143,7 +143,7 @@ ############################################################################### # It looks like this cut off some of the labels on the bottom. We can # tell Matplotlib to automatically make room for elements in the figures -# that we create. To do this we'll set the ``autolayout`` value of our +# that we create. To do this we set the ``autolayout`` value of our # rcParams. For more information on controlling the style, layout, and # other features of plots with rcParams, see # :doc:`/tutorials/introductory/customizing`. @@ -156,8 +156,9 @@ plt.setp(labels, rotation=45, horizontalalignment='right') ############################################################################### -# Next, we'll add labels to the plot. To do this with the OO interface, -# we can use the `.Artist.set` method to set properties of this Axes object. +# Next, we add labels to the plot. To do this with the OO interface, +# we can use the :meth:`.Artist.set` method to set properties of this +# Axes object. fig, ax = plt.subplots() ax.barh(group_names, group_data) @@ -186,9 +187,14 @@ ############################################################################### # For labels, we can specify custom formatting guidelines in the form of -# functions by using the :class:`ticker.FuncFormatter` class. Below we'll -# define a function that takes an integer as input, and returns a string -# as an output. +# functions. Below we define a function that takes an integer as input, and +# returns a string as an output. When used with `.Axis.set_major_formatter` or +# `.Axis.set_minor_formatter`, they will automatically create and use a +# :class:`ticker.FuncFormatter` class. +# +# For this function, the ``x`` argument is the original tick label and ``pos`` +# is the tick position. We will only use ``x`` here but both arguments are +# needed. def currency(x, pos): @@ -199,11 +205,9 @@ def currency(x, pos): s = '${:1.0f}K'.format(x*1e-3) return s -formatter = FuncFormatter(currency) - ############################################################################### -# We can then apply this formatter to the labels on our plot. To do this, -# we'll use the ``xaxis`` attribute of our axis. This lets you perform +# We can then apply this function to the labels on our plot. To do this, +# we use the ``xaxis`` attribute of our axis. This lets you perform # actions on a specific axis on our plot. fig, ax = plt.subplots(figsize=(6, 8)) @@ -213,7 +217,7 @@ def currency(x, pos): ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company', title='Company Revenue') -ax.xaxis.set_major_formatter(formatter) +ax.xaxis.set_major_formatter(currency) ############################################################################### # Combining multiple visualizations @@ -236,12 +240,12 @@ def currency(x, pos): ax.text(145000, group, "New Company", fontsize=10, verticalalignment="center") -# Now we'll move our title up since it's getting a little cramped +# Now we move our title up since it's getting a little cramped ax.title.set(y=1.05) ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company', title='Company Revenue') -ax.xaxis.set_major_formatter(formatter) +ax.xaxis.set_major_formatter(currency) ax.set_xticks([0, 25e3, 50e3, 75e3, 100e3, 125e3]) fig.subplots_adjust(right=.1) @@ -259,7 +263,7 @@ def currency(x, pos): ############################################################################### # We can then use the :meth:`figure.Figure.savefig` in order to save the figure -# to disk. Note that there are several useful flags we'll show below: +# to disk. Note that there are several useful flags we show below: # # * ``transparent=True`` makes the background of the saved figure transparent # if the format supports it. diff --git a/tutorials/text/text_intro.py b/tutorials/text/text_intro.py index e5592336d0d8..507e86cf141a 100644 --- a/tutorials/text/text_intro.py +++ b/tutorials/text/text_intro.py @@ -272,7 +272,7 @@ 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] +tickla = [f'{tick:1.2f}' for tick in ticks] axs[1].xaxis.set_ticks(ticks) axs[1].xaxis.set_ticklabels(tickla) axs[1].set_xlim(axs[0].get_xlim()) @@ -285,16 +285,16 @@ # Instead of making a list of all the tickalbels, we could have # used `matplotlib.ticker.StrMethodFormatter` (new-style ``str.format()`` # format string) or `matplotlib.ticker.FormatStrFormatter` (old-style '%' -# format string) and passed it to the ``ax.xaxis``. +# format string) and passed it to the ``ax.xaxis``. A +# `matplotlib.ticker.StrMethodFormatter` can also be created by passing a +# ``str`` without having to explicitly create the formatter. 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].xaxis.set_major_formatter('{x:1.1f}') axs[1].set_xlim(axs[0].get_xlim()) plt.show() @@ -306,10 +306,9 @@ 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) +axs[1].xaxis.set_major_formatter('±{x}°') plt.show() ############################################################################# @@ -350,26 +349,28 @@ ############################################################################## # Finally, we can specify functions for the formatter using -# `matplotlib.ticker.FuncFormatter`. +# `matplotlib.ticker.FuncFormatter`. Further, like +# `matplotlib.ticker.StrMethodFormatter`, passing a function will +# automatically create a `matplotlib.ticker.FuncFormatter`. def formatoddticks(x, pos): """Format odd tick positions.""" if x % 2: - return '%1.2f' % x + return f'{x:1.2f}' 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_formatter(formatoddticks) ax.xaxis.set_major_locator(locator) plt.show() + ############################################################################## # Dateticks # ~~~~~~~~~