Home > front end >  Legend handle to an xarray plot
Legend handle to an xarray plot

Time:02-06

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()

move xarray legend via sns.move_legend

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()
  •  Tags:  
  • Related