{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Menu\n\nUsing texts to construct a simple menu.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from dataclasses import dataclass\n\nimport matplotlib.pyplot as plt\n\nimport matplotlib.artist as artist\nimport matplotlib.patches as patches\nfrom matplotlib.typing import ColorType\n\n\n@dataclass\nclass ItemProperties:\n fontsize: float = 14\n labelcolor: ColorType = 'black'\n bgcolor: ColorType = 'yellow'\n alpha: float = 1.0\n\n\nclass MenuItem(artist.Artist):\n padx = 0.05 # inches\n pady = 0.05\n\n def __init__(self, fig, labelstr, props=None, hoverprops=None,\n on_select=None):\n super().__init__()\n\n self.set_figure(fig)\n self.labelstr = labelstr\n\n self.props = props if props is not None else ItemProperties()\n self.hoverprops = (\n hoverprops if hoverprops is not None else ItemProperties())\n if self.props.fontsize != self.hoverprops.fontsize:\n raise NotImplementedError(\n 'support for different font sizes not implemented')\n\n self.on_select = on_select\n\n # specify coordinates in inches.\n self.label = fig.text(0, 0, labelstr, transform=fig.dpi_scale_trans,\n size=props.fontsize)\n self.text_bbox = self.label.get_window_extent(\n fig.canvas.get_renderer())\n self.text_bbox = fig.dpi_scale_trans.inverted().transform_bbox(self.text_bbox)\n\n self.rect = patches.Rectangle(\n (0, 0), 1, 1, transform=fig.dpi_scale_trans\n ) # Will be updated later.\n\n self.set_hover_props(False)\n\n fig.canvas.mpl_connect('button_release_event', self.check_select)\n\n def check_select(self, event):\n over, _ = self.rect.contains(event)\n if not over:\n return\n if self.on_select is not None:\n self.on_select(self)\n\n def set_extent(self, x, y, w, h, depth):\n self.rect.set(x=x, y=y, width=w, height=h)\n self.label.set(position=(x + self.padx, y + depth + self.pady / 2))\n self.hover = False\n\n def draw(self, renderer):\n self.rect.draw(renderer)\n self.label.draw(renderer)\n\n def set_hover_props(self, b):\n props = self.hoverprops if b else self.props\n self.label.set(color=props.labelcolor)\n self.rect.set(facecolor=props.bgcolor, alpha=props.alpha)\n\n def set_hover(self, event):\n \"\"\"\n Update the hover status of event and return whether it was changed.\n \"\"\"\n b, _ = self.rect.contains(event)\n changed = (b != self.hover)\n if changed:\n self.set_hover_props(b)\n self.hover = b\n return changed\n\n\nclass Menu:\n def __init__(self, fig, menuitems):\n self.figure = fig\n\n self.menuitems = menuitems\n\n maxw = max(item.text_bbox.width for item in menuitems)\n maxh = max(item.text_bbox.height for item in menuitems)\n depth = max(-item.text_bbox.y0 for item in menuitems)\n\n x0 = 1\n y0 = 4\n\n width = maxw + 2 * MenuItem.padx\n height = maxh + MenuItem.pady\n\n for item in menuitems:\n left = x0\n bottom = y0 - maxh - MenuItem.pady\n\n item.set_extent(left, bottom, width, height, depth)\n\n fig.artists.append(item)\n y0 -= maxh + MenuItem.pady\n\n fig.canvas.mpl_connect('motion_notify_event', self.on_move)\n\n def on_move(self, event):\n if any(item.set_hover(event) for item in self.menuitems):\n self.figure.canvas.draw()\n\n\nfig = plt.figure()\nfig.subplots_adjust(left=0.3)\nprops = ItemProperties(labelcolor='black', bgcolor='yellow',\n fontsize=15, alpha=0.2)\nhoverprops = ItemProperties(labelcolor='white', bgcolor='blue',\n fontsize=15, alpha=0.2)\n\nmenuitems = []\nfor label in ('open', 'close', 'save', 'save as', 'quit'):\n def on_select(item):\n print(f'you selected {item.labelstr}')\n item = MenuItem(fig, label, props=props, hoverprops=hoverprops,\n on_select=on_select)\n menuitems.append(item)\n\nmenu = Menu(fig, menuitems)\nplt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.2" } }, "nbformat": 4, "nbformat_minor": 0 }