I cannot modify the legend of plot of a dataset made with xarray plotting function.
The code below returns No handles with labels found to put in legend.
import xarray as xr
import matplotlib.pyplot as plt
air = xr.tutorial.open_dataset("air_temperature").air
air.isel(lon=10, lat=[19, 21, 22]).plot.line(x="time", add_legend=True)
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
CodePudding user response:
You can use seaborn's sns.move_legend()
, followed by plt.tight_layout()
. sns.move_legend()
is new in seaborn 0.11.2.
import xarray as xr
import matplotlib.pyplot as plt
import seaborn as sns
air = xr.tutorial.open_dataset("air_temperature").air
air.isel(lon=10, lat=[19, 21, 22]).plot.line(x="time", add_legend=True)
sns.move_legend(plt.gca(), loc='center left', bbox_to_anchor=(1, 0.5))
plt.tight_layout()
plt.show()
PS: If you don't want to import seaborn, you could copy the function from its source. You'll need to remove a reference to sns.axisgrid.Grid
and import matplotlib as mpl; import inspect
:
import matplotlib.pyplot as plt
import matplotlib as mpl
import inspect
import xarray as xr
def move_legend(obj, loc, **kwargs):
"""
Recreate a plot's legend at a new location.
Extracted from seaborn/utils.py
"""
if isinstance(obj, mpl.axes.Axes):
old_legend = obj.legend_
legend_func = obj.legend
elif isinstance(obj, mpl.figure.Figure):
if obj.legends:
old_legend = obj.legends[-1]
else:
old_legend = None
legend_func = obj.legend
else:
err = "`obj` must be a matplotlib Axes or Figure instance."
raise TypeError(err)
if old_legend is None:
err = f"{obj} has no legend attached."
raise ValueError(err)
# Extract the components of the legend we need to reuse
handles = old_legend.legendHandles
labels = [t.get_text() for t in old_legend.get_texts()]
# Extract legend properties that can be passed to the recreation method
# (Vexingly, these don't all round-trip)
legend_kws = inspect.signature(mpl.legend.Legend).parameters
props = {k: v for k, v in old_legend.properties().items() if k in legend_kws}
# Delegate default bbox_to_anchor rules to matplotlib
props.pop("bbox_to_anchor")
# Try to propagate the existing title and font properties; respect new ones too
title = props.pop("title")
if "title" in kwargs:
title.set_text(kwargs.pop("title"))
title_kwargs = {k: v for k, v in kwargs.items() if k.startswith("title_")}
for key, val in title_kwargs.items():
title.set(**{key[6:]: val})
kwargs.pop(key)
# Try to respect the frame visibility
kwargs.setdefault("frameon", old_legend.legendPatch.get_visible())
# Remove the old legend and create the new one
props.update(kwargs)
old_legend.remove()
new_legend = legend_func(handles, labels, loc=loc, **props)
new_legend.set_title(title.get_text(), title.get_fontproperties())
air = xr.tutorial.open_dataset("air_temperature").air
air.isel(lon=10, lat=[19, 21, 22]).plot.line(x="time", add_legend=True)
move_legend(plt.gca(), loc='center left', bbox_to_anchor=(1, 0.5))
plt.tight_layout()
plt.show()