Skip to content

Commit

Permalink
displayed VLs history (#17)
Browse files Browse the repository at this point in the history
* adds a history section to the explorer
* improve explorer's elements alignments and sizes.
* updates docs' screenshots to include the history box

Signed-off-by: Christian Biasuzzi <[email protected]>
  • Loading branch information
CBiasuzzi authored Sep 17, 2024
1 parent 592b327 commit 2217af8
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 56 deletions.
Binary file modified docs/_static/img/getting_started_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/_static/img/network_explorer_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/_static/img/network_explorer_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/_static/img/network_explorer_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/user_guide/network_explorer.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Network explorer is interactive network explorer widget, built on pypowsybl-jupyter's widgets (SLD, NAD, Network map) and some standard [ipywidgets](https://ipywidgets.readthedocs.io/en/stable/index.html): select lists, tabs, etc.

Through the widget, you can select a voltage level from the list (or search of a specific one using the Filter) and the NAD and SLD diagrams for that voltage level will be displayed in the two "Network Area" and "Single Line" tabs, respectively. Both diagrams can be panned and zoomed. A third tab, 'Network map' displays the network's substations and lines on a map, if substations and lines geo data is available in the network.
Through the widget, you can select a voltage level from the list (or search of a specific one using the Filter) and the NAD and SLD diagrams for that voltage level will be displayed in the two "Network Area" and "Single Line" tabs, respectively. Both diagrams can be panned and zoomed. A third tab, 'Network map' displays the network's substations and lines on a map, if substations and lines geo data is available in the network. The last displayed voltage levels are listed in a history box, on the explorer's bottom left corner.

The following code, to be run in a notebook, first creates a network, then displays the network explorer on it.

Expand Down
6 changes: 3 additions & 3 deletions js/sldwidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ function render({ model, el }: RenderProps<SldWidgetModel>) {
svg_data,
metadata ? JSON.parse(metadata) : null,
'voltage-level',
500,
800,
600,
800,
600,
1000,
1200,
metadata ? handleNextVl : null, //callback on the next voltage arrows
metadata ? handleSwitch : null, //callback on the breakers
metadata ? handleFeeder : null, //callback on the feeders
Expand Down
138 changes: 89 additions & 49 deletions src/pypowsybl_jupyter/networkexplorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def network_explorer(network: Network, vl_id : str = None, use_name:bool = True,
network_explorer(pp.network.create_eurostag_tutorial_example1_network())
"""

sel_ctx=SelectContext(network, vl_id, use_name)
sel_ctx=SelectContext(network, vl_id, use_name, history_max_length = 10)

nad_widget=None
sld_widget=None
Expand All @@ -61,45 +61,49 @@ def network_explorer(network: Network, vl_id : str = None, use_name:bool = True,

spars=sld_parameters if sld_parameters is not None else SldParameters(use_name=use_name, nodes_infos=True)

def set_current_vl(id):
sel_ctx.extend_filtered_vls(id)
found.value=None
found.options=sel_ctx.get_filtered_vls_as_list()
sel_ctx.set_selected(id)
found.value=sel_ctx.get_selected()

def go_to_vl(event: any):
arrow_vl= str(event.clicked_nextvl)
set_current_vl(arrow_vl)
if arrow_vl != sel_ctx.get_selected():
sel_ctx.set_selected(arrow_vl, add_to_history=True)
update_select_widget(history, sel_ctx.get_selected(), sel_ctx.get_history_as_list(), on_selected_history)
update_select_widget(found, sel_ctx.get_selected() if sel_ctx.is_selected_in_filtered_vls() else None, None, on_selected)
update_explorer()
history.focus()


def toggle_switch(event: any):
idswitch = event.clicked_switch.get('id')
statusswitch = event.clicked_switch.get('switch_status')
network.update_switches(id=idswitch, open=statusswitch)
update_sld_diagram(True)
update_nad_diagram()
update_sld_diagram(sel_ctx.get_selected(), True)
update_nad_diagram(sel_ctx.get_selected())

def go_to_vl_from_map(event: any):
vl_from_map= str(event.selected_vl)
set_current_vl(vl_from_map)
if vl_from_map != sel_ctx.get_selected():
sel_ctx.set_selected(vl_from_map, add_to_history=True)
update_select_widget(history, sel_ctx.get_selected(), sel_ctx.get_history_as_list(), on_selected_history)
update_select_widget(found, sel_ctx.get_selected() if sel_ctx.is_selected_in_filtered_vls() else None, None, on_selected)
update_explorer()
history.focus()
#switch to the SLD tab
tabs_diagrams.selected_index=1

def update_nad_diagram():
def update_nad_diagram(el):
nonlocal nad_widget
if sel_ctx.get_selected() is not None:
new_diagram_data=network.get_network_area_diagram(voltage_level_ids=sel_ctx.get_selected(),
if el is not None:
new_diagram_data=network.get_network_area_diagram(voltage_level_ids=el,
depth=selected_depth, high_nominal_voltage_bound=high_nominal_voltage_bound,
low_nominal_voltage_bound=low_nominal_voltage_bound, nad_parameters=npars)
if nad_widget==None:
nad_widget=display_nad(new_diagram_data)
else:
update_nad(nad_widget,new_diagram_data)

def update_sld_diagram(kv: bool = False):
def update_sld_diagram(el, kv: bool = False):
nonlocal sld_widget
if sel_ctx.get_selected() is not None:
sld_diagram_data=network.get_single_line_diagram(sel_ctx.get_selected(), spars)
if el is not None:
sld_diagram_data=network.get_single_line_diagram(el, spars)
if sld_widget==None:
sld_widget=display_sld(sld_diagram_data, enable_callbacks=True)
sld_widget.on_nextvl(lambda event: go_to_vl(event))
Expand All @@ -108,23 +112,22 @@ def update_sld_diagram(kv: bool = False):
else:
update_sld(sld_widget, sld_diagram_data, keep_viewbox=kv, enable_callbacks=True)

def update_map():
def update_map(el):
nonlocal map_widget
if sel_ctx.get_selected() is not None:
if el is not None:
if map_widget==None:
map_widget=NetworkMapWidget(network, use_name=use_name, nominal_voltages_top_tiers_filter = nominal_voltages_top_tiers_filter, use_line_geodata = use_line_geodata)
map_widget=NetworkMapWidget(network, use_name=use_name, nominal_voltages_top_tiers_filter = nominal_voltages_top_tiers_filter)
map_widget.on_selectvl(lambda event : go_to_vl_from_map(event))

else:
map_widget.center_on_voltage_level(sel_ctx.get_selected())

map_widget.center_on_voltage_level(el)
nadslider = widgets.IntSlider(value=selected_depth, min=0, max=20, step=1, description='depth:', disabled=False,
continuous_update=False, orientation='horizontal', readout=True, readout_format='d')

def on_nadslider_changed(d):
nonlocal selected_depth
selected_depth=d['new']
update_nad_diagram()
update_nad_diagram(sel_ctx.get_selected())

nadslider.observe(on_nadslider_changed, names='value')

Expand All @@ -134,53 +137,90 @@ def on_nadslider_changed(d):
description='Filter',
disabled=False,
continuous_update=True,
layout=widgets.Layout(width='350px')
layout=widgets.Layout(flex='2%', height='100%', width='350px', margin='1px 0 0 0')
)

def update_select_widget(widget, el, elements=None, on_select=None):
if on_select:
widget.unobserve(on_select, names='value')
try:
if elements is not None:
widget.value = None
widget.options = elements

widget.value = el

finally:
if on_select:
widget.observe(on_select, names='value')


def on_text_changed(d):
nonlocal found
sel_ctx.apply_filter(d['new'])
found.value=None
found.options = sel_ctx.get_filtered_vls_as_list()
if sel_ctx.is_selected_in_filtered_vls():
found.value=sel_ctx.get_selected()
sel = sel_ctx.get_selected() if sel_ctx.is_selected_in_filtered_vls() else None
opts = sel_ctx.get_filtered_vls_as_list()
update_select_widget(found, sel, opts, on_selected)

vl_input.observe(on_text_changed, names='value')

found = widgets.Select(
options=sel_ctx.get_filtered_vls_as_list(),
value=sel_ctx.get_selected(),
value=None,
description='Found',
disabled=False,
layout=widgets.Layout(width='350px', height='670px')
layout=widgets.Layout(flex='80%', height='100%', width='350px', margin='0 0 0 0')
)

def on_selected(d):
if d['new'] != None:
sel_ctx.set_selected(d['new'])
update_nad_diagram()
update_sld_diagram()
update_map()
sel_ctx.set_selected(d['new'], add_to_history=True)
update_select_widget(history, None, sel_ctx.get_history_as_list(), on_selected_history)
update_explorer()

found.observe(on_selected, names='value')

update_nad_diagram()
update_sld_diagram()
update_map()
history = widgets.Select(
options=sel_ctx.get_history_as_list(),
value=sel_ctx.get_selected(),
description='History',
disabled=False,
layout=widgets.Layout(flex='18%', height='100%', width='350px', margin='1px 0 0 0')
)

def on_selected_history(d):
if d['new'] != None:
sel_ctx.set_selected(d['new'], add_to_history=False)
update_select_widget(found, sel_ctx.get_selected() if sel_ctx.is_selected_in_filtered_vls() else None, None, on_selected)
update_explorer()

history.observe(on_selected_history, names='value')

def update_explorer():
sel=sel_ctx.get_selected()
update_nad_diagram(sel)
update_sld_diagram(sel)
update_map(sel)

update_explorer()

voltage_levels_label=widgets.Label("Voltage levels")
spacer_label=widgets.Label("")

left_panel = widgets.VBox([vl_input, found, history], layout=widgets.Layout(width='100%', height='100%', display='flex', flex_flow='column'))

left_panel = widgets.VBox([widgets.Label('Voltage levels'), vl_input, found])

right_panel_nad = widgets.VBox([nadslider, nad_widget])
right_panel_sld = widgets.HBox([sld_widget])
right_panel_map = widgets.HBox([map_widget])
right_panel_sld = widgets.VBox([spacer_label,sld_widget])
right_panel_map = widgets.VBox([spacer_label, map_widget])

tabs_diagrams = widgets.Tab()
tabs_diagrams.children = [right_panel_nad, right_panel_sld, right_panel_map]
tabs_diagrams.titles = ['Network Area', 'Single Line', 'Network map']
tabs_diagrams.layout=widgets.Layout(width='850px', height='700px')

hbox = widgets.HBox([left_panel, tabs_diagrams])
hbox.layout.align_items='flex-end'
tabs_diagrams.layout=widgets.Layout(width='850px', height='700px', margin='0 0 0 4px')

left_vbox = widgets.VBox([voltage_levels_label, left_panel])
right_vbox = widgets.VBox([spacer_label, tabs_diagrams])

hbox = widgets.HBox([left_vbox, right_vbox])

return hbox

return hbox
19 changes: 16 additions & 3 deletions src/pypowsybl_jupyter/selectcontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
# SPDX-License-Identifier: MPL-2.0
#

from collections import OrderedDict
import pandas as pd
from collections import deque
from pypowsybl.network import Network

class SelectContext:

def __init__(self, network:Network = None, vl_id : str = None, use_name:bool = True):
def __init__(self, network:Network = None, vl_id : str = None, use_name:bool = True, history_max_length:int = -1):
self.network = network
self.use_name = use_name
self.display_attribute = 'name' if use_name else 'id'
Expand All @@ -24,14 +24,19 @@ def __init__(self, network:Network = None, vl_id : str = None, use_name:bool = T

self.apply_filter(None)

self.history = deque(maxlen=None if history_max_length == -1 else history_max_length)

self.set_selected(self.vls.index[0] if vl_id is None else vl_id)

def get_vls(self):
return self.vls

def set_selected(self, id):
def set_selected(self, id, add_to_history=True):
if id in self.vls.index:
self.selected_vl = id
last_id_from_history=self.history[0]['id'] if len(self.history)>0 else None
if add_to_history and self.selected_vl != last_id_from_history:
self.add_to_history(id)
else:
raise ValueError(f'a voltage level with id={id} does not exist in the network.')

Expand All @@ -54,3 +59,11 @@ def get_filtered_vls_as_list(self):
def extend_filtered_vls(self, id):
if (id in self.vls.index) and (id not in self.vls_filtered.index):
self.vls_filtered = pd.concat([self.vls_filtered, self.vls.loc[[id]]])

def add_to_history(self, id):
if (id in self.vls.index):
row_to_add = self.vls.loc[id].to_dict()
self.history.appendleft(row_to_add)

def get_history_as_list(self):
return [(item[self.display_attribute], item['id']) for item in self.history]

0 comments on commit 2217af8

Please sign in to comment.