Source code for pylbo.visualisation.legend_handler

import matplotlib.collections as mpl_collections
import matplotlib.lines as mpl_lines
import matplotlib.patches as mpl_patches
import numpy as np
from pylbo.utilities.toolbox import add_pickradius_to_item


[docs]class LegendHandler: """ Main handler for legend stuff. Attributes ---------- legend : ~matplotlib.legend.Legend The matplotlib legend to use. alpha_point : int, float Alpha value for non-hidden lines or points. alpha_region : int, float Alpha value for non-hidden regions. alpha_hidden : int, float Alpha value for hidden artists. marker : ~matplotlib.markers The marker to use for points. markersize : int, float Size of the marker. pickradius : int, float Radius around pickable items so pickevents are triggered. linewidth : int, float Width of drawn lines. legend_properties : dict Additional properties used when setting the legend. interactive : bool If `True`, makes the legend interactive autoscale : bool If `True`, will check if autoscale is needed when clicking the legend. """ def __init__(self, interactive): self.legend = None self.alpha_point = 0.8 self.alpha_region = 0.2 self.alpha_hidden = 0.05 self.marker = "p" self.markersize = 64 self.pickradius = 10 self.linewidth = 2 self.legend_properties = {} self.interactive = interactive self.autoscale = False self._drawn_items = [] self._legend_mapping = {} self._make_visible_by_default = False
[docs] def on_legend_pick(self, event): """ Determines what happens when the legend gets picked. Parameters ---------- event : ~matplotlib.backend_bases.PickEvent The matplotlib pick event. """ artist = event.artist if artist not in self._legend_mapping: return drawn_item = self._legend_mapping.get(artist) visible = not drawn_item.get_visible() drawn_item.set_visible(visible) if visible: if isinstance(artist, (mpl_collections.PathCollection, mpl_lines.Line2D)): artist.set_alpha(self.alpha_point) else: artist.set_alpha(self.alpha_region) else: artist.set_alpha(self.alpha_hidden) self._check_autoscaling() artist.figure.tight_layout() artist.figure.canvas.draw()
[docs] def make_legend_pickable(self): """ Makes the legend pickable, only used if interactive. """ legend_handles = self.legend.legendHandles handle_labels = [handle.get_label() for handle in legend_handles] # we need a mapping of the legend item to the actual item that was drawn for i, drawn_item in enumerate(self._drawn_items): # TODO: for some reason fill_between returns empty handles such that # the code below errors out. The try-except clause is an attempt # to fix it and _should_ work. This relies on the ordening of the # legend items when they are drawn, but should be thorougly tested. try: idx = handle_labels.index(drawn_item.get_label()) legend_item = self.legend.legendHandles[idx] except ValueError: idx = i legend_item = self.legend.legendHandles[idx] # fix empty label legend_item.set_label(drawn_item.get_label()) if not drawn_item.get_label() == legend_item.get_label(): raise ValueError( f"something went wrong in mapping legend items to drawn items. \n" f"Tried to map {legend_item} (label '{legend_item.get_label()}')" f" to {drawn_item} (label '{drawn_item.get_label()}') \n" ) add_pickradius_to_item(item=legend_item, pickradius=self.pickradius) # add an attribute to this artist to tell it's from a legend setattr(legend_item, "is_legend_item", True) # make sure colourmapping is done properly, only for continua regions if isinstance(legend_item, mpl_patches.Rectangle): legend_item.set_facecolor(drawn_item.get_facecolor()) legend_item.set_edgecolor(drawn_item.get_edgecolor()) # we make the regions invisible until clicked, or set visible as default if self._make_visible_by_default: legend_item.set_alpha(self.alpha_point) else: legend_item.set_alpha(self.alpha_hidden) drawn_item.set_visible(self._make_visible_by_default) self._legend_mapping[legend_item] = drawn_item
[docs] def add(self, item): """ Adds an item to the list of drawn items on the canvas. Parameters ---------- item : object A single object, usually a return from the matplotlib plot or scatter methods. """ if isinstance(item, (list, np.ndarray, tuple)): raise ValueError("object expected, not something list-like") self._drawn_items.append(item)
[docs] def _check_autoscaling(self): """ Checks if autoscaling is needed and if so, rescales the y-axis to the min-max value of the currently visible legend items. """ if not self.autoscale: return visible_items = [item for item in self._drawn_items if item.get_visible()] # check scaling for visible items. This explicitly implements the equilibria, # but works for general cases as well. If needed we can simply subclass # and override. ymin1, ymax1 = None, None ymin2, ymax2 = None, None for item in visible_items: if not item.get_label().startswith("d"): if ymin1 is None or ymax1 is None: ymin1, ymax1 = np.min(item.get_ydata()), np.max(item.get_ydata()) else: ymin1 = min(ymin1, np.min(item.get_ydata())) ymax1 = max(ymax1, np.max(item.get_ydata())) item.axes.set_ylim(ymin1 - abs(0.1 * ymin1), ymax1 + abs(0.1 * ymax1)) else: if ymin2 is None or ymax2 is None: ymin2, ymax2 = np.min(item.get_ydata()), np.max(item.get_ydata()) else: ymin2 = min(ymin2, np.min(item.get_ydata())) ymax2 = max(ymax2, np.max(item.get_ydata())) item.axes.set_ylim(ymin2 - abs(0.2 * ymin2), ymax2 + abs(0.2 * ymax2))