2026, Jan 02 21:02

Как сделать легенду в GeoPandas: прокси-artist в Matplotlib

Почему легенда в GeoPandas остается пустой и как исправить в Matplotlib: используем proxy artist (Patch) вместо Axes, приводим минимальный пример и рабочий код.

Получить работающую легенду в графике GeoPandas бывает неожиданно сложно. Рисунок отображается, но легенда остаётся пустой или появляется предупреждение. Причина проста: в legend надо передавать рисуемый artist, а не объект Axes, который возвращает GeoPandas.

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

import geopandas as gpd
from shapely.geometry import box, Polygon, LineString
import matplotlib.pyplot as plt
fig_map, ax_map = plt.subplots()
bounds = [9.454, 80.4, 12, 80.88]
rect = box(*bounds)
geo_frame = gpd.GeoDataFrame(geometry=[rect])
drawn = geo_frame.plot(
    ax=ax_map,
    edgecolor='red',
    facecolor='none',
    linewidth=2,
    label="user bbox query"
)
# Это не сработает: "drawn" — это Axes, а не объект для легенды
ax_map.legend(handles=[drawn])
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.show()

Matplotlib объясняет, почему это не срабатывает:

Легенда не поддерживает handles для экземпляров Axes. Вместо этого можно использовать прокси-объект (proxy artist).

Почему так происходит

GeoPandas использует Matplotlib под капотом. Вызов GeoDataFrame.plot возвращает Axes. Легенды же строятся из artists — патчей, линий или коллекций, то есть объектов, которые реально рисуются на графике. Передача Axes в legend не поддерживается и приводит к пустой легенде. Решение — дать легенде прокси‑artist с теми же стилями, что и у нарисованного объекта.

Рабочий подход с прокси‑объектом

Создайте Patch с теми же стилями, что у вашего полигона, и передайте его в legend. Размещение легенды вне области осей помогает избежать перекрытия карты и делает подпись читаемой.

import geopandas as gpd
from shapely.geometry import box
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
fig_map, ax_map = plt.subplots()
bounds = [9.454, 80.4, 12, 80.88]
rect = box(*bounds)
geo_frame = gpd.GeoDataFrame(geometry=[rect])
geo_frame.plot(
    ax=ax_map,
    edgecolor='red',
    facecolor='none',
    linewidth=2
)
legend_item = mpatches.Patch(
    facecolor='none',
    edgecolor='red',
    linewidth=2,
    label='user bbox query'
)
ax_map.legend(handles=[legend_item], bbox_to_anchor=(1.05, 1), loc='upper left')
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.tight_layout()
plt.show()

Почему это стоит запомнить

Легенды в Matplotlib зависят от передаваемых handle-объектов. Если handle не является рисуемым artist, легенда не покажет ожидаемый результат. В рабочих процессах с GeoPandas это встречается часто, потому что удобный API для построения графиков возвращает Axes, тогда как легенде нужен artist. Использование прокси‑объекта вроде Patch сохраняет точность легенды и читаемость карты. Размещение легенды за пределами области построения с помощью bbox_to_anchor помогает, когда содержимое карты плотно заполняет оси.

Вывод

Если легенда в GeoPandas пустая или не отображается, не передавайте Axes в legend. Дайте легенде прокси‑artist, повторяющий стиль вашей геометрии, и расположите её там, где есть место. Шаблон прост: отрисуйте данные, создайте соответствующий Patch (или другой artist), передайте его в legend и доверьте tight_layout расстановку отступов.