matplotlib помечает подграфики разных размеров на одинаковом расстоянии от угла каждого подграфика

Я ищу элегантное решение для размещения метки фигуры (A, B, C) над углом каждого подзаголовка с использованием matplotlib, но каждый из них должен быть на одинаковом расстоянии от угла осей подзаговора. Проблема, с которой я сталкиваюсь, заключается в том, что типичные решения для маркировки используют дробь осей, поэтому легко разместить A, B, C в одном и том же месте относительно каждого графика.

import matplotlib as mpl
import matplotlib.pyplot as plt
fig, ax = plt.subplots(2,2, figsize = (10,10))

texts = ['A', 'B', 'C', 'D']
axes = fig.get_axes()
for a,l in zip(axes, texts):
    a.annotate(l, xy=(-0.1, 1.1), xycoords="axes fraction", fontsize=15, weight = 'bold')
plt.suptitle('Label_Distance Consistent', fontsize = 20)

введите здесь описание изображения

однако, если графики имеют разные размеры, вы получите метки, которые находятся на разном расстоянии от углов графиков (из-за соотношения сторон). См., например, этикетки A и C. Я ищу хороший способ обеспечить пропорциональное расстояние меток от углов осей для панелей, содержащих несколько подграфиков размеров/соотношений сторон, и/или явно установить текст на определенном расстоянии (в дюймах или, возможно, единицах измерения координат) от осей углы.

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

fig, ax = plt.subplots(2,2, figsize = (10,10))

ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=3)
ax2 = plt.subplot2grid((3, 3), (1, 0), colspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
ax4 = plt.subplot2grid((3, 3), (2, 0))
ax5 = plt.subplot2grid((3, 3), (2, 1))

axes = fig.get_axes()
texts = ['A', 'B', 'C', 'D', 'E']
for a,l in zip(axes, texts):
    a.annotate(l, xy=(-0.1, 1.1), xycoords="axes fraction", fontsize=15, weight = 'bold')
plt.suptitle('Label Distance Inconsistent', fontsize = 20)

введите здесь описание изображения


person djakubosky    schedule 12.09.2018    source источник


Ответы (4)


Вы можете получить положение каждого подграфика и добавить текст на уровне фигуры, чтобы смещения были в долях размера фигуры, что одинаково для всех подграфиков:

X = a.get_position().x0
Y = a.get_position().y1    
fig.text(X - .04, Y + .015, l, size=15, weight='bold')

Результат: введите здесь описание изображения

person SpghttCd    schedule 13.09.2018
comment
принимая этот ответ, поскольку он решает большинство требований моего вопроса и, вероятно, удовлетворит потребности большинства людей. - person djakubosky; 13.09.2018

Вы можете использовать axes pixels вместо axes fraction в качестве ссылки и добавить отдельные высоты, чтобы компенсировать нижний левый угол точки отсчета:

Здесь для краткости только измененная строка в вашем цикле, остальная часть вашего кода нетронута:

    a.annotate(l, xy=(-40, 10 + a.bbox.height), xycoords="axes pixels", fontsize=15, weight = 'bold')

введите здесь описание изображения

person SpghttCd    schedule 12.09.2018
comment
Отличный ответ, это почти делает то, на что я надеялся. Я обнаружил, что это не работает последовательно, когда dpi не равно 80, есть ли способ объяснить это? Так что, если я увеличу свой dpi, например, до 100, метки снова не будут соответствовать осям - C будет намного дальше (по высоте) от угла ax3 (внизу справа). Есть ли способ установить текст на указанном расстоянии в дюймах (x дюймов, y дюймов) по отношению к верхним углам осей, чтобы это могло быть устойчивым к изменениям dpi/размерам рисунка? Большое спасибо за помощь! - person djakubosky; 12.09.2018
comment
Если подумать, это, похоже, не совсем работает при любом DPI, который я пытаюсь использовать, это может быть связано с версией matplotlib, потому что ваше изображение выглядит согласованным (у меня 2.2.3). Кажется, что a.bbox.height не возвращает значение, которое соответствует высоте графиков для меня. - person djakubosky; 12.09.2018
comment
Только что протестировал его с помощью matplotlib 2.2.3, и он все еще работает с a.bbox.height и axes pixels. - person SpghttCd; 13.09.2018
comment
определенно имеет смысл, что это сработает, но по какой-то причине мои надписи все еще масштабируются с размерами участков. На самом деле, даже если я нарисую их с помощью xy= (0,bbox.height) (теоретически верхний угол), буквы не будут располагаться в углах. странный. Я наблюдал другие странные результаты в этом методе ax.annotate, а именно то, что построение на xycoords = «цифровые точки» или «фигурные пиксели», похоже, не размещает текст там, где он ожидался, поэтому в моем ответе ниже я преобразовал из дисплея вернуться к дроби осей. - person djakubosky; 13.09.2018
comment
Подтверждение того, что это работает для других, с которыми я работаю, должно быть чем-то специфичным для моей конфигурации программного обеспечения. - person djakubosky; 13.09.2018

Как насчет простого добавления названия сюжетов?

(Опять же только строка, которую нужно заменить в вашем цикле:)

a.set_title(l, size=15, weight='bold', loc='left')

Результат: введите здесь описание изображения

person SpghttCd    schedule 13.09.2018
comment
Я никогда не думал об этом! Очень эффективны, если их не нужно компенсировать. - person djakubosky; 13.09.2018

Хотя оба вышеперечисленных решения работают (и являются более простыми/удовлетворяют потребности большинства людей), я решил опубликовать решение, которое я собрал воедино, позволяющее размещать текст/объекты с точным смещением в дюймах/точках от определенной точки осей. Это означает, что независимо от размера панели рисунка/точки на дюйм, текст может быть размещен на одинаковом расстоянии от углов — полезно, если вы делаете несколько рисунков разных размеров и хотите, чтобы они были согласованы (например, для публикации). По-видимому, функция matplotlib.transforms.offset_copy() была разработана для этой цели, позволяя указывать дюймы, точки (1/72 дюйма) или точки в качестве смещения.

https://matplotlib.org/examples/pylab_examples/transoffset.html

import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

fig, ax = plt.subplots(2,2, figsize = (10,7), dpi = 500)

ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=3)
ax2 = plt.subplot2grid((3, 3), (1, 0), colspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
ax4 = plt.subplot2grid((3, 3), (2, 0))
ax5 = plt.subplot2grid((3, 3), (2, 1))

axes = fig.get_axes()
texts = ['A', 'B', 'C', 'D', 'E']
for a,l in zip(axes, texts):
    a.set_xlim(0,10)
    inv = a.transData.inverted()

    # specify the number of points or inches or dots to transform by
    # places an axes transform here for the axes on which you want to pick an anchor point on
    offsetter = mpl.transforms.offset_copy(a.transAxes, fig=fig, x= -2, y= 6, units = 'points')

    # offset an anchor point - this will return display coordinates 
    corner_offset = offsetter.transform([0,1])

    # convert display coordinate to axes fraction
    axes_frac = inv.transform(corner_offset)
    a.annotate(l, xy=(axes_frac[0],axes_frac[1]), xycoords="axes fraction", fontsize=15, weight = 'bold', color='blue')

введите здесь описание изображения

или с изменением figsize/dpi

введите здесь описание изображения

person djakubosky    schedule 13.09.2018