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