The versions:
* Ubuntu 24.04
* python 3.12.3
* matplotlib 3.10.0
I've searched for this solution for days. So I describe it here for anyone who might need ist.
The goal is rather simple:
I want to create a figure with three subplots, each with an independent x-axis because I want to display data with different time periods.
I expected to get something like this:
And that is exactly what you get if execute this simple program.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from datetime import datetime
import matplotlib as mp
print(mp.__version__)
# 3.10.0
FORMAT_MAJOR = False
FORMAT_MINOR = False
# Format definitions
# not all are used
years = mdates.YearLocator() # every year
months = mdates.MonthLocator() # every month
days = mdates.DayLocator() # every day
hours = mdates.HourLocator() # every hour
years_fmt = mdates.DateFormatter('%Y')
month_fmt = mdates.DateFormatter('%m')
day_fmt = mdates.DateFormatter('%d')
hour_fmt = mdates.DateFormatter('%H')
fig, axs = plt.subplots(nrows=3, ncols=1, figsize=(187, 12), sharex="none")
datx0 = [ datetime(2025, 1, 31), datetime(2025, 2, 2), datetime(2025, 2, 3) ]
daty0 = [100, 200, 150]
datx1 = [ datetime(2025, 2, 4), datetime(2025, 2, 5), datetime(2025, 2, 7) ]
daty1 = [150, 100, 150]
datx2 = [ datetime(2025, 2, 1), datetime(2025, 2, 4), datetime(2025, 2, 5) ]
daty2 = [200, 200, 150]
axs[0].plot(datx0, daty0)
axs[1].plot(datx1, daty1)
axs[2].plot(datx2, daty2)
for pos in range(3): # 0..2
curraxs = axs[pos]
curraxs.grid(True)
if FORMAT_MAJOR:
curraxs.xaxis.set_major_locator(days)
curraxs.xaxis.set_major_formatter(day_fmt)
curraxs.tick_params(axis="x", which="major", rotation=45)
if FORMAT_MINOR:
curraxs.xaxis.set_minor_locator(hours)
curraxs.xaxis.set_minor_formatter(hour_fmt)
curraxs.tick_params(axis="x", which="minor", rotation=90)
# only 1% "slack" at each end
curraxs.set_xmargin(0.01)
print(axs[0].xaxis.get_majorticklabels())
print(axs[1].xaxis.get_majorticklabels())
print(axs[2].xaxis.get_majorticklabels())
plt.show()
As you can see, there are three data series.
- The first from 2025-1-31 to 2025-2-3.
- The second from 2025-2-4 to 2025-2-7.
- The third from 2025-2-1 to 2025-2-5.
The date ranges have been chosen to overlap slightly. The y-data has no special meaning other than to show different graphs in the subplots.
The vanishing act occurs if you try to format the x-axis labels.
This is usually done with:
import matplotlib.dates as mdates
days = mdates.DayLocator()
day_fmt = mdates.DateFormatter('%d')
axs.xaxis.set_major_locator(days)
axs.xaxis.set_major_formatter(day_fmt)
This works fine for a single axis. If you have more than one, strange things happen:
The missing x-ticks become more apparent if you set FORMAT_MINOR to True as well.
- In the first subplot the ticks for 2025-01-31 are missing.
- In the second subplot the ticks from 2025-02-05 and above are missing.
- Only the third subplot has all x-ticks.
The output of the get_majorticklabels() of the three axis at the end of the program...
print(axs[0].xaxis.get_majorticklabels())
print(axs[1].xaxis.get_majorticklabels())
print(axs[2].xaxis.get_majorticklabels())
...gives an indication of what happened:
They are all identical – using the values from the last call 2025-01-01 to 2025-02-05.
Which explains the missing parts at the beginning of the first subplot and the missing days at the end of the second.
So, how to fix this?
It seems that – contrary to what one might believe – the xxxxLocator() calls are not simply generators that produce ticks as requested. They seem to keep some kind of internal state – in this case of the last subplot – influencing all the other uses.
You have to move them into the for-loop so that for each axis a “new” xxxxLocator() is created.
...
for pos in range(3): # 0..2
curraxs = axs[pos]
curraxs.grid(True)
days = mdates.DayLocator()
hours = mdates.HourLocator()
if FORMAT_MAJOR:
curraxs.xaxis.set_major_locator(days)
curraxs.xaxis.set_major_formatter(day_fmt)
curraxs.tick_params(axis="x", which="major", rotation=45)
...
This gives the expected result: