2025, Oct 31 04:17

Как решить наложение подписей делений по Y в подграфиках Matplotlib

Устраните наложение подписей делений по Y в подграфиках Matplotlib с общей осью X: точный сдвиг меток через ScaledTranslation без изменения hspace и пределов

Когда вы располагаете подграфики Matplotlib один над другим и делите общую ось X, нижняя подпись делений по Y у верхней оси и верхняя подпись по Y у нижней оси могут наложиться друг на друга. Такое пересечение ухудшает читаемость, а добавление вертикального отступа hspace или изменение пределов данных подходит не всегда. Задача — сохранить обе подписи и аккуратно развести их.

Как воспроизвести наложение

Минимальный пример ниже создает два подграфика с общей осью X и нулевым вертикальным интервалом. Верхняя ось охватывает диапазон от −1 до 1, нижняя — от −0.01 до 0.01. Подписи делений по Y на границе соприкосновения пересекаются.

import numpy as np
import matplotlib.pyplot as plt

fig_box, ax_grid = plt.subplots(nrows=2, sharex='col', gridspec_kw={'hspace': 0, 'wspace': 0})
ax_grid[1].set_xlim([0, 10])

ax_grid[0].set_ylim(-1.0, 1.0)
ax_grid[1].set_ylim(-0.01, 0.01)

x_vals = np.linspace(0, 10, 100)

ax_grid[0].plot(x_vals, np.sin(x_vals), color="red")
ax_grid[1].plot(x_vals, 0.01 * np.sin(x_vals), color="blue")

plt.show()

Что именно пересекается и почему

Проблема не в самих позициях делений, а в их подписях — отрисованном тексте на границе между осями. Из‑за общей оси X и отсутствия вертикального отступа нижняя подпись верхней оси и верхняя подпись нижней оси занимают один и тот же участок. Увеличив hspace или расширив пределы по Y, можно избежать столкновения, но это изменит схему расположения или видимый диапазон, что подходит не всегда.

Сдвигаем подписи делений, а не сами деления

Точный способ — сместить лишь две проблемные подписи на малую величину в единицах экрана. Класс ScaledTranslation позволяет аккуратно задать вертикальный сдвиг в пунктах, не трогая данные и компоновку.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.transforms import ScaledTranslation

fig_win, ax_set = plt.subplots(nrows=2, sharex='col', gridspec_kw={'hspace': 0, 'wspace': 0})
ax_set[1].set_xlim([0, 10])

ax_set[0].set_ylim(-1.0, 1.0)
ax_set[1].set_ylim(-0.01, 0.01)

x_seq = np.linspace(0, 10, 100)

ax_set[0].plot(x_seq, np.sin(x_seq), color="red")
ax_set[1].plot(x_seq, 0.01 * np.sin(x_seq), color="blue")

# Вертикальный сдвиг на 5 pt; 5 pt = 5/72 дюйма
shift = ScaledTranslation(0, 5/72, fig_win.dpi_scale_trans)

# Чуть приподнимем нижнюю метку на верхней оси
lbl = ax_set[0].yaxis.get_majorticklabels()[0]
lbl.set_transform(lbl.get_transform() + shift)

# Чуть опустим верхнюю метку на нижней оси
lbl = ax_set[1].yaxis.get_majorticklabels()[-1]
lbl.set_transform(lbl.get_transform() - shift)

plt.show()

Чем полезен такой подход

Этот прием сохраняет диапазоны данных и плотную компоновку, возвращая читаемость. Он позволяет оставить обе подписи делений, не уводя одну колонку делений вправо и не раздувая интервалы между подграфиками. Это особенно полезно, когда рисунок должен оставаться компактным или когда для одной из панелей планируется вторая ось Y, где перенос колонки подписей мешал бы.

Итоги

Если две подписи делений по Y сталкиваются между вертикально расположенными подграфиками с общей осью X, подвиньте только конфликтующие подписи. Небольшого сдвига в пунктах через ScaledTranslation достаточно, чтобы их развести, не меняя пределов осей и hspace и сохраняя обе подписи видимыми. Приподнимите нижнюю подпись на верхней оси и опустите верхнюю на нижней — и график останется аккуратным и читабельным.

Статья основана на вопросе на StackOverflow от Toffomat и ответе от simon.