Ответ 1
Конечно, можно получить ограничивающий прямоугольник заголовка, который является элементом Text
. Это можно сделать с помощью
title = ax.set_title(...)
bb = title.get_bbox_patch()
В принципе, затем можно манипулировать ограничивающим прямоугольником, например, через bb.set_width(...)
. Однако все настройки теряются, когда matplotlib рисует заголовок на холсте. По крайней мере, так я интерпретирую метод Text
draw()
.
Я не знаю других способов установки ограничительной рамки. Например, ограничивающую рамку legend
можно установить через plt.legend(bbox_to_anchor=(0., 1.02, 1.,.102), loc=3, mode="expand")
, так что он расширяется по всему диапазону осей (см. здесь). Было бы очень полезно иметь такую же опцию для Text
. Но пока что нет.
Объект Text
позволяет установить аргумент bbox
который обычно предназначен для установки стиля ограничивающего прямоугольника. Не существует способа установить экстенты ограничивающего прямоугольника, но он принимает некоторый словарь свойств окружающего прямоугольника. И одним из принятых свойств является boxstyle
. По умолчанию это square
, но его можно задать кругом или стрелкой или другими странными фигурами.
Эти boxstyle
на самом деле являются ключом к возможному решению. Все они наследуются от BoxStyle._Base
и - как видно в нижней части руководства по аннотациям - можно определить пользовательскую форму, BoxStyle._Base
подкласс BoxStyle._Base
.
Следующее решение основано на BoxStyle._Base
подкласса BoxStyle._Base
таким образом, что он принимает ширину осей в качестве аргумента и рисует путь прямоугольника заголовка так, чтобы он имел именно эту ширину.
В качестве бонуса мы можем зарегистрировать обработчик событий таким образом, чтобы эта ширина, как только она изменялась из-за изменения размера окна, была адаптирована.
Вот код:
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from matplotlib.path import Path
from matplotlib.patches import BoxStyle
class ExtendedTextBox(BoxStyle._Base):
"""
An Extended Text Box that expands to the axes limits
if set in the middle of the axes
"""
def __init__(self, pad=0.3, width=500.):
"""
width:
width of the textbox.
Use 'ax.get_window_extent().width'
to get the width of the axes.
pad:
amount of padding (in vertical direction only)
"""
self.width=width
self.pad = pad
super(ExtendedTextBox, self).__init__()
def transmute(self, x0, y0, width, height, mutation_size):
"""
x0 and y0 are the lower left corner of original text box
They are set automatically by matplotlib
"""
# padding
pad = mutation_size * self.pad
# we add the padding only to the box height
height = height + 2.*pad
# boundary of the padded box
y0 = y0 - pad
y1 = y0 + height
_x0 = x0
x0 = _x0 +width /2. - self.width/2.
x1 = _x0 +width /2. + self.width/2.
cp = [(x0, y0),
(x1, y0), (x1, y1), (x0, y1),
(x0, y0)]
com = [Path.MOVETO,
Path.LINETO, Path.LINETO, Path.LINETO,
Path.CLOSEPOLY]
path = Path(cp, com)
return path
dpi = 80
# register the custom style
BoxStyle._style_list["ext"] = ExtendedTextBox
plt.figure(dpi=dpi)
s = pd.Series(np.random.lognormal(.001, .01, 100))
ax = s.cumprod().plot()
# set the title position to the horizontal center (0.5) of the axes
title = ax.set_title('My Log Normal Example', position=(.5, 1.02),
backgroundcolor='black', color='white')
# set the box style of the title text box toour custom box
bb = title.get_bbox_patch()
# use the axes' width as width of the text box
bb.set_boxstyle("ext", pad=0.4, width=ax.get_window_extent().width )
# Optionally: use eventhandler to resize the title box, in case the window is resized
def on_resize(event):
print "resize"
bb.set_boxstyle("ext", pad=0.4, width=ax.get_window_extent().width )
cid = plt.gcf().canvas.mpl_connect('resize_event', on_resize)
# use the same dpi for saving to file as for plotting on screen
plt.savefig(__file__+".png", dpi=dpi)
plt.show()
На случай, если кто-то заинтересован в более легком решении, есть также возможность поэкспериментировать с mutation_aspect
ограничивающей рамки заголовка, которая, очевидно, остается неизменной при рисовании заголовка. Хотя сам mutation_aspect
основном только изменяет высоту блока, можно использовать очень большой отступ для блока и установить для mutation_aspect
очень маленькое число, чтобы в конце блок казался расширенным по ширине. Очевидным недостатком этого решения является то, что значения для отступа и аспекта должны быть найдены методом проб и ошибок и будут меняться для разных размеров шрифта и рисунка. В моем случае значения mutation_aspect = 0.04
и pad=11.9
дают желаемый результат, но в других системах они, конечно, могут отличаться.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
s = pd.Series(np.random.lognormal(.001, .01, 100))
ax = s.cumprod().plot()
title = ax.set_title('My Log Normal Example', position=(.5, 1.02),
backgroundcolor='black', color='white',
verticalalignment="bottom", horizontalalignment="center")
title._bbox_patch._mutation_aspect = 0.04
title.get_bbox_patch().set_boxstyle("square", pad=11.9)
plt.tight_layout()
plt.savefig(__file__+".png")
plt.show()