{ "cells": [ { "cell_type": "markdown", "id": "t5-header", "metadata": {}, "source": [ "# Tutorial 5: Developing an Extension (Dev)\n", "\n", "This tutorial shows how to create custom extensions for scivianna panels, adding new functionality and UI elements." ] }, { "cell_type": "markdown", "id": "t5-intro", "metadata": {}, "source": [ "## Introduction\n", "\n", "**Extensions** in scivianna are modular components that add new functionality to panels. They can:\n", "- Add custom UI widgets (buttons, sliders, text inputs)\n", "- Receive callbacks from the panel (mouse events, data changes, viewport updates)\n", "- Modify panel rendering behavior\n", "- Access and manipulate slave data\n", "\n", "## Extension Architecture\n", "\n", "Extensions are built on a single base class:\n", "- **`Extension`**: Base class providing event callbacks, GUI creation, and serialization hooks\n", "\n", "## Extension Lifecycle\n", "\n", "1. **Initialization**: Extension is created with references to slave, plotter, and panel\n", "2. **GUI Creation**: `make_gui()` returns the widget to display in the extension tab\n", "3. **Event Handling**: Extension callback methods are called when events occur:\n", " - File loading (`on_file_load`)\n", " - Field changes (`on_field_change`)\n", " - Data updates (`on_updated_data`)\n", " - Viewport changes (`on_range_change`, `on_frame_change`)\n", " - Mouse events (`on_mouse_move`, `on_mouse_clic`)\n", "4. **Options Providing**: `provide_options()` sends options to the slave\n", "5. **Serialization**: `to_json()`/`from_json()` for saving/restoring state" ] }, { "cell_type": "markdown", "id": "t5-example", "metadata": {}, "source": [ "## Complete Example: DummyTestExtension\n", "\n", "Here's a complete example of a simple extension that displays a title and receives mouse events. This is the same extension used in the test suite.\n", "\n", "### How It Works\n", "\n", "1. **`__init__`**: Calls parent constructor with name, icon, and references; initializes event history lists\n", "2. **`make_gui`**: Returns a Panel Material UI Column with a title and description text\n", "3. **`on_mouse_move`**: Called when mouse moves over the plot - logs the event with coordinates and cell ID\n", "4. **`on_mouse_clic`**: Called when user clicks on the plot - logs the click event" ] }, { "cell_type": "code", "execution_count": null, "id": "t5-example-code", "metadata": {}, "outputs": [], "source": [ "from typing import Tuple, Union, List\n", "import panel as pn\n", "import panel_material_ui as pmui\n", "\n", "from scivianna.extension.extension import Extension\n", "from scivianna.plotter_2d.generic_plotter import Plotter2D\n", "from scivianna.panel.panel_2d import Panel2D\n", "from scivianna.slave import ComputeSlave\n", "from scivianna.data.data2d import Data2D\n", "\n", "\n", "# SVG icon for the extension (optional)\n", "ICON_SVG = \"\"\"\n", "\n", " \n", "\n", "\"\"\"\n", "\n", "\n", "class DummyTestExtension(Extension):\n", " \"\"\"A simple extension that displays a title and tracks mouse events.\n", " \n", " This is a minimal example showing how to:\n", " - Create an extension with a custom GUI\n", " - Receive mouse move and click callbacks\n", " - Track event history for debugging or display\n", " \"\"\"\n", " \n", " def __init__(\n", " self,\n", " slave: \"ComputeSlave\",\n", " plotter: \"Plotter2D\",\n", " panel: \"Panel2D\"\n", " ):\n", " \"\"\"Initialize the extension.\n", " \n", " Parameters\n", " ----------\n", " slave : ComputeSlave\n", " Slave computing the displayed data\n", " plotter : Plotter2D\n", " Figure plotter\n", " panel : Panel2D\n", " Panel to which the extension is attached\n", " \"\"\"\n", " # Call parent constructor with required parameters\n", " super().__init__(\n", " title=\"TestExtension\",\n", " icon=ICON_SVG,\n", " slave=slave,\n", " plotter=plotter,\n", " panel=panel,\n", " )\n", "\n", " self.description = \"\"\"\n", " This is a simple test extension that displays a title\n", " and tracks mouse events.\n", " \"\"\"\n", "\n", " self.iconsize = \"1.5em\"\n", "\n", " # Event history for tracking\n", " self._mouse_move_history: List[Tuple] = []\n", " self._mouse_click_history: List[Tuple] = []\n", "\n", " def make_gui(self) -> pn.viewable.Viewable:\n", " \"\"\"Create the GUI widget for this extension.\n", " \n", " Returns\n", " -------\n", " pn.viewable.Viewable\n", " Panel component to display in the extension tab\n", " \"\"\"\n", " return pmui.Column(\n", " pmui.Typography(\"Test Extension\"),\n", " pmui.Text(\"Move or click on the plot to see events.\"),\n", " margin=10\n", " )\n", "\n", " def on_mouse_move(\n", " self,\n", " screen_location: Tuple[float, float],\n", " space_location: Tuple[float, float, float],\n", " cell_id: Union[str, int],\n", " ):\n", " \"\"\"Called when the mouse moves over the plot.\n", " \n", " This callback is triggered continuously as the user moves their cursor\n", " over the visualization area. Use it for tooltips, highlighting,\n", " or real-time coordinate display.\n", " \n", " Parameters\n", " ----------\n", " screen_location : Tuple[float, float]\n", " Mouse position in screen coordinates (pixels from top-left)\n", " space_location : Tuple[float, float, float]\n", " Mouse position in 3D world coordinates (x, y, z)\n", " cell_id : Union[str, int]\n", " ID of the cell currently under the mouse cursor\n", " \"\"\"\n", " event = (screen_location, space_location, cell_id)\n", " self._mouse_move_history.append(event)\n", " print(f\"Mouse moved: screen={screen_location}, space={space_location}, cell={cell_id}\")\n", "\n", " def on_mouse_clic(\n", " self,\n", " screen_location: Tuple[float, float],\n", " space_location: Tuple[float, float, float],\n", " cell_id: Union[str, int],\n", " ):\n", " \"\"\"Called when the mouse clicks on the plot.\n", " \n", " This callback is triggered when the user clicks (taps) on the\n", " visualization area. Use it for selection, annotations, or\n", " triggering actions on specific cells.\n", " \n", " Parameters\n", " ----------\n", " screen_location : Tuple[float, float]\n", " Click position in screen coordinates (pixels from top-left)\n", " space_location : Tuple[float, float, float]\n", " Click position in 3D world coordinates (x, y, z)\n", " cell_id : Union[str, int]\n", " ID of the cell that was clicked\n", " \"\"\"\n", " event = (screen_location, space_location, cell_id)\n", " self._mouse_click_history.append(event)\n", " print(f\"Mouse clicked: screen={screen_location}, space={space_location}, cell={cell_id}\")\n", "\n", "\n", "# Test the extension\n", "if __name__ == \"__main__\":\n", " from scivianna.slave import ComputeSlave\n", " from scivianna.panel.panel_2d import Panel2D\n", " \n", " # First, you need an interface (see Tutorial 4)\n", " from test_interface import DummyTestInterface\n", " \n", " # Create slave and panel\n", " slave = ComputeSlave(DummyTestInterface)\n", " panel = Panel2D(slave, name=\"My View\")\n", " \n", " # Extensions are automatically created from the interface's extensions list\n", " print(f\"Extensions: {[e.title for e in panel.extensions]}\")" ] }, { "cell_type": "markdown", "id": "t5-linking-extension", "metadata": {}, "source": [ "## Linking an Extension to an Interface\n", "\n", "To automatically include your extension when using a specific interface, add it to the `extensions` class attribute:" ] }, { "cell_type": "code", "execution_count": null, "id": "t5-linking-code", "metadata": {}, "outputs": [], "source": [ "from scivianna.interface.generic_interface import Geometry2DPolygon\n", "\n", "\n", "class MyInterfaceWithExtension(Geometry2DPolygon):\n", " \"\"\"An interface that includes a custom extension.\"\"\"\n", " \n", " # List your extensions here - they will be automatically instantiated\n", " # when a Panel2D is created with this interface\n", " extensions = [DummyTestExtension]\n", " \n", " # ... implement other required methods ...\n", "\n", "\n", "# When you create a panel with this interface:\n", "# from scivianna.slave import ComputeSlave\n", "# from scivianna.panel.panel_2d import Panel2D\n", "#\n", "# slave = ComputeSlave(MyInterfaceWithExtension)\n", "# panel = Panel2D(slave)\n", "# \n", "# Your extension will automatically appear in the panel's extension tab." ] }, { "cell_type": "markdown", "id": "t5-api-reference", "metadata": {}, "source": [ "## API Reference: Methods Table\n", "\n", "| Method | Description |\n", "|--------|-------------|\n", "| `__init__(title, icon, slave, plotter, panel)` | Initialize extension with title, icon, and panel references. Required. |\n", "| `make_gui()` | Return Panel widget for the extension tab. Default: None. |\n", "| `on_file_load(file_path, file_key)` | Called when a file is loaded. Default: no-op. |\n", "| `on_field_change(field_name)` | Called when displayed field changes. Default: no-op. |\n", "| `on_updated_data(data)` | Called when displayed data updates. Can modify data. Default: no-op. |\n", "| `on_range_change(u_bounds, v_bounds, w_value)` | Called when viewport bounds change (zoom/pan). Default: no-op. |\n", "| `on_frame_change(u_vector, v_vector)` | Called when viewport orientation changes (rotation). Default: no-op. |\n", "| `on_mouse_move(screen_location, space_location, cell_id)` | Called on mouse hover over plot. Default: no-op. |\n", "| `on_mouse_clic(screen_location, space_location, cell_id)` | Called on mouse click on plot. Default: no-op. |\n", "| `provide_options()` | Return options dict to send to slave. Default: {}. |\n", "| `to_json()` | Return state dict for serialization. Default: {}. |\n", "| `from_json(extension, info_dict)` | Restore extension from state dict. Default: unchanged. |\n", "\n", "### Class Attributes\n", "\n", "| Attribute | Type | Description |\n", "|-----------|------|-------------|\n", "| `title` | str | Extension title displayed at top of sidebar |\n", "| `description` | str | Short description of the extension |\n", "| `icon` | str | SVG icon string or file path for tabs |\n", "| `slave` | ComputeSlave | Reference to computing slave |\n", "| `plotter` | Plotter2D | Reference to figure plotter |\n", "| `panel` | VisualizationPanel | Reference to parent panel |\n", "| `iconsize` | str | Icon size CSS (default: \"6em\") |" ] }, { "cell_type": "markdown", "id": "t5-summary", "metadata": {}, "source": [ "## Summary\n", "\n", "To create a custom extension:\n", "\n", "1. **Inherit from `Extension`**\n", "\n", "2. **Implement `__init__`**:\n", " - Call `super().__init__(title, icon, slave, plotter, panel)`\n", " - Set `description` and `iconsize`\n", "\n", "3. **Implement `make_gui()`**:\n", " - Return a Panel component (e.g., `pmui.Column`, `pn.Row`)\n", " - Add widgets like sliders, buttons, text inputs\n", "\n", "4. **(Optional) Implement event callbacks**:\n", " - `on_file_load(file_path, file_key)` - React to file loading\n", " - `on_field_change(field_name)` - React to field changes\n", " - `on_updated_data(data)` - Modify data before plotting\n", " - `on_range_change(u_bounds, v_bounds, w_value)` - React to zoom/pan\n", " - `on_frame_change(u_vector, v_vector)` - React to rotation\n", " - `on_mouse_move(screen_location, space_location, cell_id)` - Hover effects\n", " - `on_mouse_clic(screen_location, space_location, cell_id)` - Click actions\n", "\n", "5. **(Optional) Implement utility methods**:\n", " - `provide_options()` - Send options to slave\n", " - `to_json()` / `from_json()` - Save/restore state\n", "\n", "6. **Link to an interface** by adding it to the `extensions` class attribute:\n", " ```python\n", " class MyInterface(Geometry2DPolygon):\n", " extensions = [MyCustomExtension]\n", " ```" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }