""" ==== Menu ==== Using texts to construct a simple menu. """ from dataclasses import dataclass import matplotlib.pyplot as plt import matplotlib.artist as artist import matplotlib.patches as patches from matplotlib.typing import ColorType @dataclass class ItemProperties: fontsize: float = 14 labelcolor: ColorType = 'black' bgcolor: ColorType = 'yellow' alpha: float = 1.0 class MenuItem(artist.Artist): padx = 0.05 # inches pady = 0.05 def __init__(self, fig, labelstr, props=None, hoverprops=None, on_select=None): super().__init__() self.set_figure(fig) self.labelstr = labelstr self.props = props if props is not None else ItemProperties() self.hoverprops = ( hoverprops if hoverprops is not None else ItemProperties()) if self.props.fontsize != self.hoverprops.fontsize: raise NotImplementedError( 'support for different font sizes not implemented') self.on_select = on_select # specify coordinates in inches. self.label = fig.text(0, 0, labelstr, transform=fig.dpi_scale_trans, size=props.fontsize) self.text_bbox = self.label.get_window_extent( fig.canvas.get_renderer()) self.text_bbox = fig.dpi_scale_trans.inverted().transform_bbox(self.text_bbox) self.rect = patches.Rectangle( (0, 0), 1, 1, transform=fig.dpi_scale_trans ) # Will be updated later. self.set_hover_props(False) fig.canvas.mpl_connect('button_release_event', self.check_select) def check_select(self, event): over, _ = self.rect.contains(event) if not over: return if self.on_select is not None: self.on_select(self) def set_extent(self, x, y, w, h, depth): self.rect.set(x=x, y=y, width=w, height=h) self.label.set(position=(x + self.padx, y + depth + self.pady / 2)) self.hover = False def draw(self, renderer): self.rect.draw(renderer) self.label.draw(renderer) def set_hover_props(self, b): props = self.hoverprops if b else self.props self.label.set(color=props.labelcolor) self.rect.set(facecolor=props.bgcolor, alpha=props.alpha) def set_hover(self, event): """ Update the hover status of event and return whether it was changed. """ b, _ = self.rect.contains(event) changed = (b != self.hover) if changed: self.set_hover_props(b) self.hover = b return changed class Menu: def __init__(self, fig, menuitems): self.figure = fig self.menuitems = menuitems maxw = max(item.text_bbox.width for item in menuitems) maxh = max(item.text_bbox.height for item in menuitems) depth = max(-item.text_bbox.y0 for item in menuitems) x0 = 1 y0 = 4 width = maxw + 2 * MenuItem.padx height = maxh + MenuItem.pady for item in menuitems: left = x0 bottom = y0 - maxh - MenuItem.pady item.set_extent(left, bottom, width, height, depth) fig.artists.append(item) y0 -= maxh + MenuItem.pady fig.canvas.mpl_connect('motion_notify_event', self.on_move) def on_move(self, event): if any(item.set_hover(event) for item in self.menuitems): self.figure.canvas.draw() fig = plt.figure() fig.subplots_adjust(left=0.3) props = ItemProperties(labelcolor='black', bgcolor='yellow', fontsize=15, alpha=0.2) hoverprops = ItemProperties(labelcolor='white', bgcolor='blue', fontsize=15, alpha=0.2) menuitems = [] for label in ('open', 'close', 'save', 'save as', 'quit'): def on_select(item): print(f'you selected {item.labelstr}') item = MenuItem(fig, label, props=props, hoverprops=hoverprops, on_select=on_select) menuitems.append(item) menu = Menu(fig, menuitems) plt.show()