diff --git a/galleries/examples/ticks/multilevel_ticks.py b/galleries/examples/ticks/multilevel_ticks.py new file mode 100644 index 000000000000..5b8d0ada5ae2 --- /dev/null +++ b/galleries/examples/ticks/multilevel_ticks.py @@ -0,0 +1,99 @@ +""" +========================= +Multilevel (nested) ticks +========================= + +Sometimes we want another level of tick labels on an axis, perhaps to indicate +a grouping of the ticks. + +Matplotlib does not provide an automated way to do this, but it is relatively +straightforward to annotate below the main axis. + +These examples use `.Axes.secondary_xaxis`, which is one approach. It has the +advantage that we can use Matplotlib Locators and Formatters on the axis that +does the grouping if we want. + +This first example creates a secondary xaxis and manually adds the ticks and +labels using `.Axes.set_xticks`. Note that the tick labels have a newline +(e.g. ``"\nOughts"``) at the beginning of them to put the second-level tick +labels below the main tick labels. +""" + +import matplotlib.pyplot as plt +import numpy as np + +import matplotlib.dates as mdates + +rng = np.random.default_rng(19680801) + +fig, ax = plt.subplots(layout='constrained', figsize=(4, 4)) + +ax.plot(np.arange(30)) + +sec = ax.secondary_xaxis(location=0) +sec.set_xticks([5, 15, 25], labels=['\nOughts', '\nTeens', '\nTwenties']) + +# %% +# This second example adds a second level of annotation to a categorical axis. +# Here we need to note that each animal (category) is assigned an integer, so +# ``cats`` is at x=0, ``dogs`` at x=1 etc. Then we place the ticks on the +# second level on an x that is at the middle of the animal class we are trying +# to delineate. +# +# This example also adds tick marks between the classes by adding a second +# secondary xaxis, and placing long, wide ticks at the boundaries between the +# animal classes. + +fig, ax = plt.subplots(layout='constrained', figsize=(7, 4)) + +ax.plot(['cats', 'dogs', 'pigs', 'snakes', 'lizards', 'chickens', + 'eagles', 'herons', 'buzzards'], + rng.normal(size=9), 'o') + +# label the classes: +sec = ax.secondary_xaxis(location=0) +sec.set_xticks([1, 3.5, 6.5], labels=['\n\nMammals', '\n\nReptiles', '\n\nBirds']) +sec.tick_params('x', length=0) + +# lines between the classes: +sec2 = ax.secondary_xaxis(location=0) +sec2.set_xticks([-0.5, 2.5, 4.5, 8.5], labels=[]) +sec2.tick_params('x', length=40, width=1.5) +ax.set_xlim(-0.6, 8.6) + +# %% +# Dates are another common place where we may want to have a second level of +# tick labels. In this last example, we take advantage of the ability to add +# an automatic locator and formatter to the secondary xaxis, which means we do +# not need to set the ticks manually. +# +# This example also differs from the above, in that we placed it at a location +# below the main axes ``location=-0.075`` and then we hide the spine by setting +# the line width to zero. That means that our formatter no longer needs the +# carriage returns of the previous two examples. + +fig, ax = plt.subplots(layout='constrained', figsize=(7, 4)) + +time = np.arange(np.datetime64('2020-01-01'), np.datetime64('2020-03-31'), + np.timedelta64(1, 'D')) + +ax.plot(time, rng.random(size=len(time))) + +# just format the days: +ax.xaxis.set_major_formatter(mdates.DateFormatter('%d')) + +# label the months: +sec = ax.secondary_xaxis(location=-0.075) +sec.xaxis.set_major_locator(mdates.MonthLocator(bymonthday=1)) + +# note the extra spaces in the label to align the month label inside the month. +# Note that this could have been done by changing ``bymonthday`` above as well: +sec.xaxis.set_major_formatter(mdates.DateFormatter(' %b')) +sec.tick_params('x', length=0) +sec.spines['bottom'].set_linewidth(0) + +# label the xaxis, but note for this to look good, it needs to be on the +# secondary xaxis. +sec.set_xlabel('Dates (2020)') + +plt.show()