2025, Dec 14 12:02

Смещение экспоненты оси Y в matplotlib для стопок подграфиков

Экспонента смещения по оси Y в matplotlib перекрывает данные? Разбираем причину и даем решение: принудительная отрисовка и замена смещения аннотацией.

Складывать несколько подграфиков в matplotlib кажется простым — до тех пор, пока текст смещения по оси Y (экспонента в научной записи) не оказывается прямо посередине графика сверху. При общих осях X и плотной компоновке стандартное размещение этого смещения может перекрываться с данными. Простой вызов set_position для текста смещения может выглядеть как бессмысленный — будто ничего не происходит, — что сбивает с толку, когда нужно прижать подпись под верхней рамкой оси.

Минимальный пример, воспроизводящий проблему

from matplotlib import pyplot as plt
from numpy import linspace, sqrt, pi

fig, axes = plt.subplots(3, 1, sharex=True)
plt.subplots_adjust(hspace=0)


def zr_len(w_zero, lam):
    return pi * w_zero**2 / lam


def waist_zero(lam, L_len, R_len):
    return sqrt(lam / pi * sqrt(L_len * (R_len - L_len)))


def beam_w(z_pos, lam):
    return waist_zero(lam, 0.55, 1) * sqrt(1 + (z_pos / zr_len(waist_zero(lam, 0.55, 1), lam))**2)

x_vals = linspace(0, 1, 5)
y_vals = beam_w(x_vals, 5.54e-6)

for idx in range(3):
    axes[idx].set_xlabel("z / m")
    axes[idx].set_ylabel("$d(w)$ / mm")
    axes[idx].ticklabel_format(axis='y', style='sci', scilimits=(0, 0))
    axes[idx].grid("both", linestyle=":")

    # Попытка сдвинуть экспоненту смещения
    axes[idx].get_yaxis().get_offset_text().set_position((0, 0.7))

axes[1].errorbar(x_vals, y_vals, xerr=0.025, yerr=0.2*1e-3, color="black", linestyle="")

plt.show()

Что на самом деле происходит

Текст смещения пуст, пока не отрисован холст, из‑за чего кажется, что перемещение не работает. После отрисовки меняются и содержимое, и координаты текста. В интерактивном режиме это выглядит так:

>>> axes[2].get_yaxis().get_offset_text()
Text(0, 0.7, '')
>>> fig.canvas.draw()
>>> axes[1].get_yaxis().get_offset_text()
Text(0, 303.3666666666667, '1e−3')

Иными словами, до рендеринга перемещать нечего — строки с экспонентой ещё нет; а после рендеринга matplotlib сам переставляет подпись. Поэтому прямой вызов set_position кажется неэффективным.

Сведения из обсуждения показывают различия между версиями: проблему наблюдали на matplotlib 3.9.4 в Arch Linux, тогда как другой пользователь на 3.8.4 сообщил, что изменение координат через set_position действительно сдвигало текст. Приведённый ниже обходной путь от этого не зависит: он просто заменяет смещение фиксированной аннотацией.

Практическое решение: сначала отрисовать, затем заменить смещение аннотацией

Принудительная отрисовка гарантирует, что экспонента будет вычислена. После этого можно считать текст, разместить его в нужном месте в координатах, привязанных к осям, и скрыть исходный текст смещения, чтобы он не накладывался на данные.

from matplotlib import pyplot as plt
from numpy import linspace, sqrt, pi

fig, axes = plt.subplots(3, 1, sharex=True)
plt.subplots_adjust(hspace=0)


def zr_len(w_zero, lam):
    return pi * w_zero**2 / lam


def waist_zero(lam, L_len, R_len):
    return sqrt(lam / pi * sqrt(L_len * (R_len - L_len)))


def beam_w(z_pos, lam):
    return waist_zero(lam, 0.55, 1) * sqrt(1 + (z_pos / zr_len(waist_zero(lam, 0.55, 1), lam))**2)

x_vals = linspace(0, 1, 5)
y_vals = beam_w(x_vals, 5.54e-6)

for idx in range(3):
    axes[idx].set_xlabel("z / m")
    axes[idx].set_ylabel("$d(w)$ / mm")
    axes[idx].ticklabel_format(axis='y', style='sci', scilimits=(0, 0))
    axes[idx].grid("both", linestyle=":")

axes[1].errorbar(x_vals, y_vals, xerr=0.025, yerr=0.2*1e-3, color="black", linestyle="")

# Принудительно вычислить и разместить текст смещения
fig.canvas.draw()

# Заменить текст смещения пользовательской аннотацией, позиционированной внутри области осей
for idx in range(3):
    y_off = axes[idx].get_yaxis().get_offset_text()
    axes[idx].annotate(y_off.get_text(), xy=(0.01, 0.85), xycoords='axes fraction')
    y_off.set_visible(False)

plt.show()

Почему это важно

В стопках графиков расположение подписей напрямую влияет на читаемость. Экспоненту научной записи легко не заметить, когда она наслаивается на кривые или соседние оси, а неверно прочитанный масштаб за секунду обесценивает анализ. Явный контроль за местом экспоненты — небольшое изменение с непропорционально большим выигрышем в ясности.

Выводы

Если перенос подписи смещения по оси Y как будто игнорируется, проверьте её после принудительного рендеринга. Когда холст отрисован, экспонента уже существует, и её можно заменить аннотацией в координатах долей осей, после чего исходную подпись скрыть. Так вы избежите наложений в макетах с общими осями и сохраните индикатор порядка величины там, где ему и место.