#matplotlib
#matplotlib
Вопрос:
Проблема
Мое приложение matplotlib генерирует определяемые пользователем динамические изображения, и поэтому такие вещи, как текст заголовка страницы, могут быть разной длины. Я хочу иметь возможность указать ограничивающую рамку для matplotlib, а затем автоматически масштабировать размер шрифта, чтобы текст помещался в эту ограничивающую рамку. Мое приложение использует только серверную часть AGG.
Мое решение для взлома
Я наименее острый инструмент в наборе инструментов, но вот что я придумал для решения этой проблемы. Я перебираю начальный размер шрифта 50
, а затем перебираю вниз, пока не подумаю, что смогу поместить текст в поле.
def fitbox(fig, text, x0, x1, y0, y1, **kwargs):
"""Fit text into a NDC box."""
figbox = fig.get_window_extent().transformed(
fig.dpi_scale_trans.inverted())
# need some slop for decimal comparison below
px0 = x0 * fig.dpi * figbox.width - 0.15
px1 = x1 * fig.dpi * figbox.width 0.15
py0 = y0 * fig.dpi * figbox.height - 0.15
py1 = y1 * fig.dpi * figbox.height 0.15
# print("px0: %s px1: %s py0: %s py1: %s" % (px0, px1, py0, py1))
xanchor = x0
if kwargs.get('ha', '') == 'center':
xanchor = x0 (x1 - x0) / 2.
yanchor = y0
if kwargs.get('va', '') == 'center':
yanchor = y0 (y1 - y0) / 2.
txt = fig.text(
xanchor, yanchor, text,
fontsize=50, ha=kwargs.get('ha', 'left'),
va=kwargs.get('va', 'bottom'),
color=kwargs.get('color', 'k')
)
for fs in range(50, 1, -2):
txt.set_fontsize(fs)
tbox = txt.get_window_extent(fig.canvas.get_renderer())
# print("fs: %s tbox: %s" % (fs, str(tbox)))
if (tbox.x0 >= px0 and tbox.x1 < px1 and tbox.y0 >= py0 and
tbox.y1 <= py1):
break
return txt
Итак, я могу вызвать эту функцию следующим образом
fitbox(fig, "Hello there, this is my title!", 0.1, 0.99, 0.95, 0.99)
Вопрос / Запрос обратной связи
- Предлагает ли matplotlib лучшее встроенное решение для этой проблемы?
- Какие-либо существенные недостатки этого подхода? Производительность не ощущается как прерыватель игры. Вероятно, я должен сделать так, чтобы эта функция позволяла указывать координаты в пределах одной
axes
, а не общей цифры. Возможно, это уже работает 🙂
Кроме того, мне нравится, как некоторые другие приложения для построения графиков позволяют указывать размер шрифта в безразмерных координатах отображения. Например, PyNGL. Таким образом, вы можете установить его fontsize=0.04
, например.
Спасибо.
Комментарии:
1. Какой объект находится
fig
в вашем коде? В любом случае решение не кажется слишком плохим. Подход такой же , как и для ячеек таблицы. Что, в свою очередь, означает, что вы можете использовать таблицу для достижения того же эффекта, но только еслиfig
используемый вами объект определен в тех же координатах, что и таблица (?).2. рис
plt.gcf()
. Спасибо за ваш отзыв. Я рад видеть тот же код в таблице, интересно.3. Привет, вы нашли какой-нибудь более эффективный способ автоматической подгонки текста в поле? У меня есть аналогичная реализация, но она неэффективна, особенно когда используется для добавления метки в древовидную карту. Моя реализация находится в ответе.
4. Спасибо, что поделились @Z-Y.L, я не нашел ничего более эффективного: (
Ответ №1:
Моя реализация автоматической подгонки текста в поле:
def text_with_autofit(self, txt, xy, width, height, *,
transform=None,
ha='center', va='center',
min_size=1, adjust=0,
**kwargs):
if transform is None:
if isinstance(self, Axes):
transform = self.transData
if isinstance(self, Figure):
transform = self.transFigure
x_data = {'center': (xy[0] - width/2, xy[0] width/2),
'left': (xy[0], xy[0] width),
'right': (xy[0] - width, xy[0])}
y_data = {'center': (xy[1] - height/2, xy[1] height/2),
'bottom': (xy[1], xy[1] height),
'top': (xy[1] - height, xy[1])}
(x0, y0) = transform.transform((x_data[ha][0], y_data[va][0]))
(x1, y1) = transform.transform((x_data[ha][1], y_data[va][1]))
# rectange region size to constrain the text
rect_width = x1 - x0
rect_height = y1- y0
fig = self.get_figure() if isinstance(self, Axes) else self
dpi = fig.dpi
rect_height_inch = rect_height / dpi
fontsize = rect_height_inch * 72
if isinstance(self, Figure):
text = self.text(*xy, txt, ha=ha, va=va, transform=transform,
**kwargs)
if isinstance(self, Axes):
text = self.annotate(txt, xy, ha=ha, va=va, xycoords=transform,
**kwargs)
while fontsize > min_size:
text.set_fontsize(fontsize)
bbox = text.get_window_extent(fig.canvas.get_renderer())
if bbox.width < rect_width:
break;
fontsize -= 1
if fig.get_constrained_layout():
text.set_fontsize(fontsize adjust 0.5)
if fig.get_tight_layout():
text.set_fontsize(fontsize adjust)
return text