Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

displayed VLs history #17

Merged
merged 5 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
flo-dup marked this conversation as resolved.
Show resolved Hide resolved

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
flo-dup marked this conversation as resolved.
Show resolved Hide resolved
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(
flo-dup marked this conversation as resolved.
Show resolved Hide resolved
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]
Loading