Skip to content

Commit

Permalink
updates diagram viewer dependencies; enables select and move callback…
Browse files Browse the repository at this point in the history
…s in NadWidget

Signed-off-by: Christian Biasuzzi <[email protected]>
  • Loading branch information
CBiasuzzi committed Sep 20, 2024
1 parent 2217af8 commit 9f76836
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 21 deletions.
88 changes: 88 additions & 0 deletions examples/demo_nad_features.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "747ca44b-4aea-461b-8046-e7ce940297f8",
"metadata": {},
"outputs": [],
"source": [
"from pypowsybl_jupyter import display_nad\n",
"import ipywidgets as widgets\n",
"import pypowsybl as pp"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "438c80d3-bf89-4eca-af8e-b9799d903d8c",
"metadata": {},
"outputs": [],
"source": [
"network = pp.network.create_four_substations_node_breaker_network()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aa321947-692e-4016-bf76-590628637a3e",
"metadata": {},
"outputs": [],
"source": [
"#declare some example callbacks, to simply log the events in a output box\n",
"out1 = widgets.Output()\n",
"def print_infos(data): \n",
" with out1:\n",
" print(data)\n",
"print_infos('Events log:')\n",
"\n",
"def test_select_node_callback(event):\n",
" print_infos('Selected node: ' + str(event.selected_node))\n",
"\n",
"def test_move_node_callback(event):\n",
" print_infos('Moved node: ' + str(event.moved_node))\n",
"\n",
"def test_move_text_node_callback(event):\n",
" print_infos('Moved text node: ' + str(event.moved_text_node))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "63745ac5-e1f2-4182-a03b-5bdbe41c3b3e",
"metadata": {},
"outputs": [],
"source": [
"#create a NAD widget for a network and activate the callbacks\n",
"nad_widget=display_nad(network.get_network_area_diagram(depth=4), enable_callbacks=True)\n",
"nad_widget.on_select_node(test_select_node_callback)\n",
"nad_widget.on_move_node(test_move_node_callback)\n",
"nad_widget.on_move_text_node(test_move_text_node_callback)\n",
"\n",
"#finally, display the widgets\n",
"widgets.HBox([nad_widget, out1])"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
85 changes: 84 additions & 1 deletion js/nadwidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,103 @@ import { NetworkAreaDiagramViewer } from '@powsybl/diagram-viewer';

interface NadWidgetModel {
diagram_data: any;
selected_node: any;
moved_node: any;
moved_text_node: any;
}

function render({ model, el }: RenderProps<NadWidgetModel>) {
const handleSelectNode = (equipmentId: string, nodeId: string) => {
model.set('selected_node', {
equipment_id: equipmentId,
node_id: nodeId,
});
model.save_changes();
model.send({ event: 'select_node' });
};

const handleMoveNode = (
equipmentId: string,
nodeId: string,
x: number,
y: number,
xOrig: number,
yOrig: number
) => {
model.set('moved_node', {
equipment_id: equipmentId,
node_id: nodeId,
x: x,
y: y,
x_orig: xOrig,
y_orig: yOrig,
});
model.save_changes();
model.send({ event: 'move_node' });
};

const handleMoveTextNode = (
equipmentId: string,
nodeId: string,
textNodeId: string,
shiftX: number,
shiftY: number,
shiftXOrig: number,
shiftYOrig: number,
connectionShiftX: number,
connectionShiftY: number,
connectionShiftXOrig: number,
connectionShiftYOrig: number
) => {
model.set('moved_text_node', {
equipment_id: equipmentId,
node_id: nodeId,
text_node_id: textNodeId,
shift_x: shiftX,
shift_y: shiftY,
shift_x_orig: shiftXOrig,
shift_y_orig: shiftYOrig,
connection_shift_x: connectionShiftX,
connection_shift_y: connectionShiftY,
connection_shift_x_orig: connectionShiftXOrig,
connection_shift_y_orig: connectionShiftYOrig,
});
model.save_changes();
model.send({ event: 'move_text_node' });
};

function render_diagram(model: any): any {
const diagram_data = model.get('diagram_data');
const svg_data = diagram_data['svg_data']; //svg content
const is_invalid_lf = diagram_data['invalid_lf'];
const is_enabled_callbacks = diagram_data['enable_callbacks'];

const el_div = document.createElement('div');
el_div.classList.add('svg-nad-viewer-widget');

el_div.classList.toggle('invalid-lf', is_invalid_lf);

new NetworkAreaDiagramViewer(el_div, svg_data, 800, 600, 800, 600);
new NetworkAreaDiagramViewer(
el_div,
svg_data,
800,
600,
800,
600,
handleMoveNode,
handleMoveTextNode,
handleSelectNode,
is_enabled_callbacks,
false,
null
);

// prevents the default jupyter-lab's behavior (it already uses the shift+click combination)
el_div.addEventListener('mousedown', function (event: MouseEvent) {
if (event.shiftKey) {
event.preventDefault();
}
});

return el_div;
}
Expand Down
18 changes: 4 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"lint": "eslint . --ext js,ts,jsx --max-warnings 0"
},
"dependencies": {
"@powsybl/diagram-viewer": "0.4.0"
"@powsybl/diagram-viewer": "0.5.5"
},
"devDependencies": {
"@anywidget/types": "^0.1.6",
Expand Down
52 changes: 47 additions & 5 deletions src/pypowsybl_jupyter/nadwidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,55 @@
import anywidget
import traitlets

from ipywidgets import (
CallbackDispatcher
)

class NadWidget(anywidget.AnyWidget):
_esm = pathlib.Path(__file__).parent / "static" / "nadwidget.js"
_css = pathlib.Path(__file__).parent / "static" / "nadwidget.css"

diagram_data = traitlets.Dict().tag(sync=True)
selected_node = traitlets.Dict().tag(sync=True)
moved_node = traitlets.Dict().tag(sync=True)
moved_text_node = traitlets.Dict().tag(sync=True)

def __init__(self, **kwargs):
super().__init__(**kwargs)

self._on_select_node_handler = CallbackDispatcher()
self._on_move_node_handler = CallbackDispatcher()
self._on_move_text_node_handler = CallbackDispatcher()
super().on_msg(self._handle_nadwidget_msgs)

def _handle_nadwidget_msgs(self, _, content, buffers):
if content.get('event', '') == 'select_node':
self.on_select_node_msg()
elif content.get('event', '') == 'move_node':
self.on_move_node_msg()
elif content.get('event', '') == 'move_text_node':
self.on_move_text_node_msg()

# select node
def on_select_node_msg(self):
self._on_select_node_handler(self)

def on_select_node(self, callback, remove=False):
self._on_select_node_handler.register_callback(callback, remove=remove)

# move node
def on_move_node_msg(self):
self._on_move_node_handler(self)

def on_move_node(self, callback, remove=False):
self._on_move_node_handler.register_callback(callback, remove=remove)

# move text node
def on_move_text_node_msg(self):
self._on_move_text_node_handler(self)

def on_move_text_node(self, callback, remove=False):
self._on_move_text_node_handler.register_callback(callback, remove=remove)

def _get_svg_string(svg) -> str:
if isinstance(svg, str):
return svg
Expand All @@ -31,13 +71,14 @@ def _get_svg_string(svg) -> str:
else:
raise ValueError('svg argument should be a string or provide a _repr_svg_ method.')

def display_nad(svg, invalid_lf: bool = False) -> NadWidget:
def display_nad(svg, invalid_lf: bool = False, enable_callbacks: bool = False) -> NadWidget:
"""
Displays a NAD's SVG with support for panning and zooming.
Args:
svg: the input SVG, as str or class providing an svg and metadata representation
invalid_lf: When True the opacity style for some of the displayed info's (e.g., active and reactive power) is decreased, making them barely visible in the diagram.
enable_callbacks: if true, enable the callbacks for moving and selecting nodes in the diagram.
Returns:
A jupyter widget allowing to zoom and pan the SVG.
Expand All @@ -48,16 +89,17 @@ def display_nad(svg, invalid_lf: bool = False) -> NadWidget:
display_nad(network.get_network_area_diagram())
"""
return NadWidget(diagram_data= {"svg_data": _get_svg_string(svg), "invalid_lf": invalid_lf})
return NadWidget(diagram_data= {"svg_data": _get_svg_string(svg), "invalid_lf": invalid_lf, "enable_callbacks": enable_callbacks})

def update_nad(nadwidget, svg, invalid_lf: bool = False):
def update_nad(nadwidget, svg, invalid_lf: bool = False, enable_callbacks: bool = False):
"""
Updates an existing NAD widget with a new SVG content
Args:
nadwidget: the existing widget to update
svg: the input NAD's SVG
invalid_lf: When True the opacity style for some of the displayed info's (e.g., active and reactive power) is decreased, making them barely visible in the diagram.
enable_callbacks: if true, enable the callbacks for moving and selecting nodes in the diagram.
Examples:
Expand All @@ -67,4 +109,4 @@ def update_nad(nadwidget, svg, invalid_lf: bool = False):
"""

svg_value=_get_svg_string(svg)
nadwidget.diagram_data= {"svg_data": svg_value, "invalid_lf": invalid_lf}
nadwidget.diagram_data= {"svg_data": svg_value, "invalid_lf": invalid_lf, "enable_callbacks": enable_callbacks}

0 comments on commit 9f76836

Please sign in to comment.