|
| 1 | +""" |
| 2 | +========================= |
| 3 | +Multilevel (nested) ticks |
| 4 | +========================= |
| 5 | +
|
| 6 | +Sometimes we want another level of tick labels on an axis, perhaps to indicate |
| 7 | +a grouping of the ticks. |
| 8 | +
|
| 9 | +Matplotlib does not provide an automated way to do this, but it is relatively |
| 10 | +straightforward to annotate below the main axis. |
| 11 | +
|
| 12 | +These examples use `.Axes.secondary_xaxis`, which is one approach. It has the |
| 13 | +advantage that we can use Matplotlib Locators and Formatters on the axis that |
| 14 | +does the grouping if we want. |
| 15 | +
|
| 16 | +This first example creates a secondary xaxis and manually adds the ticks and |
| 17 | +labels using `.Axes.set_xticks`. Note that the tick labels have a newline |
| 18 | +(e.g. ``"\nOughts"``) at the beginning of them to put the second-level tick |
| 19 | +labels below the main tick labels. |
| 20 | +""" |
| 21 | + |
| 22 | +import matplotlib.pyplot as plt |
| 23 | +import numpy as np |
| 24 | + |
| 25 | +import matplotlib.dates as mdates |
| 26 | + |
| 27 | +rng = np.random.default_rng(19680801) |
| 28 | + |
| 29 | +fig, ax = plt.subplots(layout='constrained', figsize=(4, 4)) |
| 30 | + |
| 31 | +ax.plot(np.arange(30)) |
| 32 | + |
| 33 | +sec = ax.secondary_xaxis(location=0) |
| 34 | +sec.set_xticks([5, 15, 25], labels=['\nOughts', '\nTeens', '\nTwenties']) |
| 35 | + |
| 36 | +# %% |
| 37 | +# This second example adds a second level of annotation to a categorical axis. |
| 38 | +# Here we need to note that each animal (category) is assigned an integer, so |
| 39 | +# ``cats`` is at x=0, ``dogs`` at x=1 etc. Then we place the ticks on the |
| 40 | +# second level on an x that is at the middle of the animal class we are trying |
| 41 | +# to delineate. |
| 42 | +# |
| 43 | +# This example also adds tick marks between the classes by adding a second |
| 44 | +# secondary xaxis, and placing long, wide ticks at the boundaries between the |
| 45 | +# animal classes. |
| 46 | + |
| 47 | +fig, ax = plt.subplots(layout='constrained', figsize=(7, 4)) |
| 48 | + |
| 49 | +ax.plot(['cats', 'dogs', 'pigs', 'snakes', 'lizards', 'chickens', |
| 50 | + 'eagles', 'herons', 'buzzards'], |
| 51 | + rng.normal(size=9), 'o') |
| 52 | + |
| 53 | +# label the classes: |
| 54 | +sec = ax.secondary_xaxis(location=0) |
| 55 | +sec.set_xticks([1, 3.5, 6.5], labels=['\n\nMammals', '\n\nReptiles', '\n\nBirds']) |
| 56 | +sec.tick_params('x'
8000
, length=0) |
| 57 | + |
| 58 | +# lines between the classes: |
| 59 | +sec2 = ax.secondary_xaxis(location=0) |
| 60 | +sec2.set_xticks([-0.5, 2.5, 4.5, 8.5], labels=[]) |
| 61 | +sec2.tick_params('x', length=40, width=1.5) |
| 62 | +ax.set_xlim(-0.6, 8.6) |
| 63 | + |
| 64 | +# %% |
| 65 | +# Dates are another common place where we may want to have a second level of |
| 66 | +# tick labels. In this last example, we take advantage of the ability to add |
| 67 | +# an automatic locator and formatter to the secondary xaxis, which means we do |
| 68 | +# not need to set the ticks manually. |
| 69 | +# |
| 70 | +# This example also differs from the above, in that we placed it at a location |
| 71 | +# below the main axes ``location=-0.075`` and then we hide the spine by setting |
| 72 | +# the line width to zero. That means that our formatter no longer needs the |
| 73 | +# carriage returns of the previous two examples. |
| 74 | + |
| 75 | +fig, ax = plt.subplots(layout='constrained', figsize=(7, 4)) |
| 76 | + |
| 77 | +time = np.arange(np.datetime64('2020-01-01'), np.datetime64('2020-03-31'), |
| 78 | + np.timedelta64(1, 'D')) |
| 79 | + |
| 80 | +ax.plot(time, rng.random(size=len(time))) |
| 81 | + |
| 82 | +# just format the days: |
| 83 | +ax.xaxis.set_major_formatter(mdates.DateFormatter('%d')) |
| 84 | + |
| 85 | +# label the months: |
| 86 | +sec = ax.secondary_xaxis(location=-0.075) |
| 87 | +sec.xaxis.set_major_locator(mdates.MonthLocator(bymonthday=1)) |
| 88 | + |
| 89 | +# note the extra spaces in the label to align the month label inside the month. |
| 90 | +# Note that this could have been done by changing ``bymonthday`` above as well: |
| 91 | +sec.xaxis.set_major_formatter(mdates.DateFormatter(' %b')) |
| 92 | +sec.tick_params('x', length=0) |
| 93 | +sec.spines['bottom'].set_linewidth(0) |
| 94 | + |
| 95 | +# label the xaxis, but note for this to look good, it needs to be on the |
| 96 | +# secondary xaxis. |
| 97 | +sec.set_xlabel('Dates (2020)') |
| 98 | + |
| 99 | +plt.show() |
0 commit comments