Графики являются мощным инструментом визуализации данных. В Matplotlib легенда играет ключевую роль, позволяя сопоставить визуальные элементы графика (линии, маркеры, цвета) с соответствующими метками данных. Это критически важно для понимания того, что именно отображается на графике, особенно когда присутствует несколько серий данных.
Что такое легенда в Matplotlib и зачем она нужна?
Легенда – это небольшой блок на графике, содержащий образцы визуальных элементов (например, отрезки линий или маркеры) и текстовые метки, которые описывают каждую серию данных. Она необходима для идентификации различных наборов данных, представленных на одном графике. Без легенды сложный график с несколькими линиями или столбцами может быть совершенно непонятным.
Проблема размещения легенды по умолчанию
По умолчанию Matplotlib пытается автоматически найти наилучшее место для размещения легенды, чтобы она минимально перекрывала данные графика. В простых случаях этот автоматический механизм работает достаточно хорошо. Однако на более сложных графиках, с большим количеством данных или нестандартным расположением элементов, автоматическое размещение часто приводит к перекрытию важной информации, делая график менее читаемым или даже вводящим в заблуждение.
bboxtoanchor: определение и основные параметры
Для решения проблемы неоптимального автоматического размещения легенды Matplotlib предоставляет параметр bbox_to_anchor в функции legend(). Этот параметр дает пользователю полный контроль над тем, где именно будет размещена легенда на графике.
bbox_to_anchor определяет точку привязки (anchor point) для легенды. В сочетании с параметром loc (который по умолчанию равен 'best', но при использовании bbox_to_anchor часто устанавливается явно) он указывает Matplotlib, какой угол или сторона ограничивающего прямоугольника легенды должен быть размещен в указанной точке привязки.
Наиболее часто bbox_to_anchor принимает tuple вида (x, y) или (x, y, width, height), где x, y, width, height задаются в координатах, определенных параметром bbox_transform (по умолчанию это координаты осей – axes fraction).
(x, y): Указывает координаты точки привязки.(x, y, width, height): Задает ограничивающий прямоугольник для легенды, полезно для более тонкого контроля над размером и положением.
Основы использования bboxtoanchor
Давайте рассмотрим базовый пример создания графика с легендой и поэтапно покажем, как использовать bbox_to_anchor для изменения ее положения.
Создание простого графика с легендой
Представим, что у нас есть данные о посещениях и конверсиях на сайте за несколько дней.
import matplotlib.pyplot as plt
import numpy as np
# Данные: дни, посещения, конверсии
days: np.ndarray = np.arange(1, 11) # 10 дней
visits: np.ndarray = np.array([150, 200, 220, 180, 250, 280, 260, 300, 320, 350])
conversions: np.ndarray = np.array([5, 8, 7, 6, 10, 12, 11, 14, 15, 16])
def create_basic_plot(days: np.ndarray, visits: np.ndarray, conversions: np.ndarray) -> plt.Figure:
"""Создает базовый график посещений и конверсий.
Args:
days: Массив дней.
visits: Массив данных о посещениях.
conversions: Массив данных о конверсиях.
Returns:
Объект Figure Matplotlib.
"""
fig, ax1 = plt.subplots(figsize=(10, 6))
# Первая ось Y для посещений
ax1.plot(days, visits, 'b-', label='Посещения')
ax1.set_xlabel('День')
ax1.set_ylabel('Посещения', color='b')
ax1.tick_params(axis='y', labelcolor='b')
# Вторая ось Y для конверсий
ax2 = ax1.twinx()
ax2.plot(days, conversions, 'r-', label='Конверсии')
ax2.set_ylabel('Конверсии', color='r')
ax2.tick_params(axis='y', labelcolor='r')
# Добавление легенды ( Matplotlib попытается разместить ее автоматически)
# Объединяем хэндлы и метки с обеих осей
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(lines + lines2, labels + labels2, loc='best')
plt.title('Динамика посещений и конверсий')
fig.tight_layout() # Автоматически корректирует положение элементов
return fig
# Отображение графика с автоматическим размещением легенды
# fig = create_basic_plot(days, visits, conversions)
# plt.show()
В этом примере ax2.legend(..., loc='best') показывает легенду, и Matplotlib сам выбирает, по его мнению, лучшее место.
Применение bboxtoanchor для перемещения легенды
Теперь давайте явно укажем место для легенды с помощью bbox_to_anchor. Мы хотим разместить ее, например, справа от графика.
import matplotlib.pyplot as plt
import numpy as np
# Данные: дни, посещения, конверсии (те же, что и выше)
days: np.ndarray = np.arange(1, 11)
visits: np.ndarray = np.array([150, 200, 220, 180, 250, 280, 260, 300, 320, 350])
conversions: np.ndarray = np.array([5, 8, 7, 6, 10, 12, 11, 14, 15, 16])
def create_plot_with_bbox(days: np.ndarray, visits: np.ndarray, conversions: np.ndarray) -> plt.Figure:
"""Создает график посещений и конверсий с легендой, размещенной с помощью bbox_to_anchor.
Args:
days: Массив дней.
visits: Массив данных о посещениях.
conversions: Массив данных о конверсиях.
Returns:
Объект Figure Matplotlib.
"""
fig, ax1 = plt.subplots(figsize=(10, 6))
ax1.plot(days, visits, 'b-', label='Посещения')
ax1.set_xlabel('День')
ax1.set_ylabel('Посещения', color='b')
ax1.tick_params(axis='y', labelcolor='b')
ax2 = ax1.twinx()
ax2.plot(days, conversions, 'r-', label='Конверсии')
ax2.set_ylabel('Конверсии', color='r')
ax2.tick_params(axis='y', labelcolor='r')
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# Размещение легенды справа от графика
# bbox_to_anchor=(1.05, 1) - точка (1.05, 1) в координатах осей (правый верхний угол осей + небольшой отступ вправо)
# loc='upper left' - верхний левый угол легенды размещается в точке (1.05, 1)
ax2.legend(lines + lines2, labels + labels2, bbox_to_anchor=(1.05, 1), loc='upper left')
plt.title('Динамика посещений и конверсий с легендой справа')
fig.tight_layout() # Автоматически корректирует положение элементов
return fig
# Отображение графика с легендой справа
# fig = create_plot_with_bbox(days, visits, conversions)
# plt.show()
В этом коде bbox_to_anchor=(1.05, 1) задает координаты точки привязки. loc='upper left' указывает Matplotlib разместить верхний левый угол ограничивающего прямоугольника легенды именно в этой точке (1.05, 1). Координата 1.05 по X немного правее правого края осей (который соответствует X=1.0 в координатах осей), а 1 по Y соответствует верхнему краю осей.
Различные варианты координат для bboxtoanchor (axes fraction, data, etc.)
По умолчанию bbox_to_anchor использует координаты осей (axes fraction), где (0,0) – это левый нижний угол осей, а (1,1) – правый верхний. Это наиболее распространенный и удобный способ размещения легенды относительно самого графика.
Однако можно использовать и другие системы координат, управляя параметром bbox_transform:
ax.transAxes: Координаты осей (значение по умолчанию).(0,0)— левый нижний угол осей,(1,1)— правый верхний.ax.transData: Координаты данных.(0,0)— начало координат данных,(Xmax, Ymax)— точка, соответствующая максимальным значениям по осям X и Y. Этот способ менее удобен для размещения легенды вне графика, так как координаты зависят от масштаба данных.fig.transFigure: Координаты фигуры.(0,0)— левый нижний угол всей фигуры (окна),(1,1)— правый верхний. Позволяет разместить легенду в любой точке окна, независимо от положения осей.
Пример использования fig.transFigure:
import matplotlib.pyplot as plt
import numpy as np
# Данные (те же)
days: np.ndarray = np.arange(1, 11)
visits: np.ndarray = np.array([150, 200, 220, 180, 250, 280, 260, 300, 320, 350])
conversions: np.ndarray = np.array([5, 8, 7, 6, 10, 12, 11, 14, 15, 16])
def create_plot_with_figure_bbox(days: np.ndarray, visits: np.ndarray, conversions: np.ndarray) -> plt.Figure:
"""Создает график посещений и конверсий с легендой, размещенной в координатах фигуры.
Args:
days: Массив дней.
visits: Массив данных о посещениях.
conversions: Массив данных о конверсиях.
Returns:
Объект Figure Matplotlib.
"""
fig, ax1 = plt.subplots(figsize=(10, 6))
ax1.plot(days, visits, 'b-', label='Посещения')
ax1.set_xlabel('День')
ax1.set_ylabel('Посещения', color='b')
ax1.tick_params(axis='y', labelcolor='b')
ax2 = ax1.twinx()
ax2.plot(days, conversions, 'r-', label='Конверсии')
ax2.set_ylabel('Конверсии', color='r')
ax2.tick_params(axis='y', labelcolor='r')
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# Размещение легенды в координатах фигуры
# bbox_to_anchor=(0.5, -0.15) - точка в координатах фигуры (центр по X, немного ниже нижнего края фигуры)
# loc='upper center' - верхняя центральная часть легенды размещается в этой точке
# bbox_transform=fig.transFigure - используем систему координат фигуры
fig.legend(lines + lines2, labels + labels2, bbox_to_anchor=(0.5, -0.15), loc='upper center', bbox_transform=fig.transFigure)
plt.title('Динамика посещений и конверсий с легендой ниже фигуры')
fig.tight_layout(rect=[0, 0.1, 1, 1]) # Оставляем место для легенды снизу
return fig
# Отображение графика с легендой ниже фигуры
# fig = create_plot_with_figure_bbox(days, visits, conversions)
# plt.show()
Обратите внимание, что при размещении легенды вне осей, особенно используя fig.transFigure или координаты осей за пределами [0,1], может потребоваться корректировка размещения самих осей с помощью fig.tight_layout() или ручной установки их позиции (fig.add_axes() или ax.set_position()) для предотвращения обрезки легенды.
Расширенные возможности bboxtoanchor
Сочетание bbox_to_anchor с параметром loc и использование tuple с размерами расширяет возможности точного позиционирования.
Управление положением легенды относительно anchor point
Как уже упоминалось, bbox_to_anchor задает точку привязки, а loc определяет, какая часть ограничивающего прямоугольника легенды будет помещена в эту точку. Значение loc может быть строкой (например, 'upper left', 'lower right', 'center') или кодом положения (например, 1 для 'upper right', 3 для 'lower left', 10 для 'center').
loc='upper left': Верхний левый угол легенды в точке привязки.loc='lower right': Нижний правый угол легенды в точке привязки.loc='center': Центр легенды в точке привязки.
Экспериментируя с комбинациями bbox_to_anchor (координаты) и loc (точка на легенде, привязываемая к этим координатам), можно добиться любого желаемого расположения.
Например, чтобы разместить легенду в правом нижнем углу осей (точка (1,0) в координатах осей), вы можете использовать:
# ... код создания осей ax1, ax2 ...
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# bbox_to_anchor=(1, 0) - правый нижний угол осей
# loc='lower right' - нижний правый угол легенды в точке (1,0)
ax2.legend(lines + lines2, labels + labels2, bbox_to_anchor=(1, 0), loc='lower right')
А чтобы разместить ее в центре осей (точка (0.5, 0.5)):
# ... код создания осей ax1, ax2 ...
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# bbox_to_anchor=(0.5, 0.5) - центр осей
# loc='center' - центр легенды в точке (0.5, 0.5)
ax2.legend(lines + lines2, labels + labels2, bbox_to_anchor=(0.5, 0.5), loc='center')
Использование tuple для bboxtoanchor: ширина и высота рамки
bbox_to_anchor также может принимать tuple из четырех элементов: (x, y, width, height). В этом случае он определяет не просто точку, а прямоугольную область, внутри которой Matplotlib попытается разместить легенду. Это полезно, когда нужно гарантировать, что легенда находится в определенной зоне и не выходит за ее пределы.
При использовании tuple (x, y, width, height):
(x, y): Координаты левого нижнего угла целевого прямоугольника.width,height: Ширина и высота целевого прямоугольника.
loc при этом определяет, где внутри этого целевого прямоугольника будет размещена легенда. Например, loc='best' попытается найти лучшее место внутри (x, y, width, height), а loc='upper right' разместит легенду в правом верхнем углу этого прямоугольника.
import matplotlib.pyplot as plt
import numpy as np
# Данные (те же)
days: np.ndarray = np.arange(1, 11)
visits: np.ndarray = np.array([150, 200, 220, 180, 250, 280, 260, 300, 320, 350])
conversions: np.ndarray = np.array([5, 8, 7, 6, 10, 12, 11, 14, 15, 16])
def create_plot_with_bbox_tuple(days: np.ndarray, visits: np.ndarray, conversions: np.ndarray) -> plt.Figure:
"""Создает график с легендой, ограниченной заданным прямоугольником.
Args:
days: Массив дней.
visits: Массив данных о посещениях.
conversions: Массив данных о конверсиях.
Returns:
Объект Figure Matplotlib.
"""
fig, ax1 = plt.subplots(figsize=(10, 6))
ax1.plot(days, visits, 'b-', label='Посещения')
ax1.set_xlabel('День')
ax1.set_ylabel('Посещения', color='b')
ax1.tick_params(axis='y', labelcolor='b')
ax2 = ax1.twinx()
ax2.plot(days, conversions, 'r-', label='Конверсии')
ax2.set_ylabel('Конверсии', color='r')
ax2.tick_params(axis='y', labelcolor='r')
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# Определение прямоугольной области для легенды
# bbox_to_anchor=(0.6, 0.7, 0.3, 0.2) - прямоугольник с левым нижним углом (0.6, 0.7),
# шириной 0.3 и высотой 0.2 в координатах осей.
# loc='best' - Matplotlib найдет лучшее место ВНУТРИ этого прямоугольника.
ax2.legend(lines + lines2, labels + labels2, bbox_to_anchor=(0.6, 0.7, 0.3, 0.2), loc='best')
plt.title('Динамика посещений и конверсий с легендой в заданном прямоугольнике')
fig.tight_layout()
return fig
# fig = create_plot_with_bbox_tuple(days, visits, conversions)
# plt.show()
Этот способ полезен, если вам нужно, чтобы легенда занимала определенное место, не выходя за границы, но вы все еще хотите дать Matplotlib некоторую свободу в выборе оптимального расположения внутри этой области.
Размещение легенды за пределами осей графика
Часто легенду удобнее разместить рядом с графиком, а не внутри него, чтобы избежать перекрытия данных. Это достигается использованием bbox_to_anchor с координатами вне диапазона [0,1] в системе ax.transAxes или с использованием fig.transFigure.
Примеры:
- Справа от осей:
bbox_to_anchor=(1.05, 1), loc='upper left'(как в примере выше) - Снизу от осей:
bbox_to_anchor=(0., -0.15), loc='upper left'(левый верхний угол легенды под левым нижним углом осей с отступом) - Сверху от осей:
bbox_to_anchor=(0., 1.02), loc='lower left'(нижний левый угол легенды над верхним левым углом осей с отступом) - Слева от осей:
bbox_to_anchor=(-0.15, 1), loc='upper right'(правый верхний угол легенды слева от верхнего левого угла осей с отступом)
Важно помнить, что при размещении легенды за пределами осей необходимо убедиться, что фигура (окно) достаточно велика, чтобы вместить и оси, и легенду. fig.tight_layout() часто помогает автоматически скорректировать положение осей, но иногда требуется более тонкая настройка с помощью rect в tight_layout() или ручная установка позиции осей.
Практические примеры использования bboxtoanchor
Рассмотрим несколько распространенных сценариев размещения легенды.
Размещение легенды в правом верхнем углу графика
Это одно из стандартных положений, часто используемых Matplotlib по умолчанию, но явное указание дает уверенность.
# ... код создания осей ax1, ax2 ...
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# bbox_to_anchor=(1, 1) - правый верхний угол осей
# loc='upper right' - верхний правый угол легенды в точке (1,1)
ax2.legend(lines + lines2, labels + labels2, bbox_to_anchor=(1, 1), loc='upper right')
Размещение легенды под графиком
Полезно, когда на графике много данных, и легенда внутри будет мешать.
# ... код создания осей ax1, ax2 ...
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# bbox_to_anchor=(0.5, -0.15) - точка под центром нижней границы осей (в координатах осей)
# loc='upper center' - верхняя центральная часть легенды в этой точке
# bbox_transform=ax1.transAxes - явно указываем координаты осей (по умолчанию)
fig.legend(lines + lines2, labels + labels2, bbox_to_anchor=(0.5, -0.15), loc='upper center', bbox_transform=ax1.transAxes)
# Важно: Adjust layout to make space for the legend below axes
fig.tight_layout(rect=[0, 0.1, 1, 1]) # rect=[left, bottom, right, top] normalized figure coordinates
Обратите внимание на использование fig.legend() вместо ax.legend(), когда легенда размещается вне осей. Это более надежный способ размещения элементов на уровне фигуры. Использование rect в tight_layout() сжимает область осей, освобождая место снизу.
Размещение легенды слева от графика с использованием различных координат
Разместим легенду слева от осей, используя координаты осей и фигуры для сравнения.
Используя координаты осей:
# ... код создания осей ax1, ax2 ...
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# bbox_to_anchor=(-0.15, 1) - точка слева от верхнего левого угла осей
# loc='upper right' - правый верхний угол легенды в этой точке
fig.legend(lines + lines2, labels + labels2, bbox_to_anchor=(-0.15, 1), loc='upper right', bbox_transform=ax1.transAxes)
fig.tight_layout(rect=[0.15, 0, 1, 1]) # Освобождаем место слева
Используя координаты фигуры:
# ... код создания осей ax1, ax2 ...
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
# bbox_to_anchor=(0.05, 0.5) - точка в координатах фигуры (слева, по центру по высоте)
# loc='center left' - левый центр легенды в этой точке
fig.legend(lines + lines2, labels + labels2, bbox_to_anchor=(0.05, 0.5), loc='center left', bbox_transform=fig.transFigure)
fig.tight_layout(rect=[0.15, 0, 1, 1]) # Освобождаем место слева (для самих осей)
Выбор между ax.transAxes и fig.transFigure зависит от того, хотите ли вы привязать легенду к осям или к самой фигуре. ax.transAxes обычно более предсказуем при работе с одним графиком, но fig.transFigure может быть полезен при сложном расположении нескольких осей.
Создание сложных графиков с несколькими легендами и bboxtoanchor
В некоторых случаях может потребоваться разместить несколько легенд, например, если разные серии данных логически группируются. Для этого можно вызвать ax.legend() или fig.legend() несколько раз, каждый раз указывая подмножество artist’ов и соответствующую позицию с помощью bbox_to_anchor.
Пример (упрощенный, без полных данных):
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# Серии данных
line1, = ax.plot([1, 2, 3], [1, 2, 1], label='Группа A - 1')
line2, = ax.plot([1, 2, 3], [1, 2, 2], label='Группа A - 2')
line3, = ax.plot([1, 2, 3], [3, 2, 3], label='Группа B - 1', linestyle='--')
# Первая легенда для Группы A
# bbox_to_anchor=(1.05, 1), loc='upper left'
legend1 = ax.legend(handles=[line1, line2], loc='upper left', bbox_to_anchor=(1.05, 1))
# Добавляем первую легенду обратно в axes artists, чтобы она не исчезла при создании второй
ax.add_artist(legend1)
# Вторая легенда для Группы B
# bbox_to_anchor=(1.05, 0.7), loc='upper left'
legend2 = ax.legend(handles=[line3], loc='upper left', bbox_to_anchor=(1.05, 0.7))
plt.title('График с несколькими легендами')
fig.tight_layout(rect=[0, 0, 0.85, 1]) # Оставляем место справа
# plt.show()
Здесь мы создаем две легенды, используя ax.legend() с разными bbox_to_anchor. Важный шаг – добавление первой легенды как