Skip to content

Commit 042052b

Browse files
Reorganize visualizer menu and add show-arrows option (#83)
* Reorganize visualizer menu and add show-arrows option Signed-off-by: Thijs Baaijen <[email protected]> * Add test Signed-off-by: Thijs Baaijen <[email protected]> * Rewrite code and apply some suggestions from review Signed-off-by: Thijs Baaijen <[email protected]> * switch to enum Signed-off-by: Thijs Baaijen <[email protected]> * more fixes Signed-off-by: Thijs Baaijen <[email protected]> * Update src/power_grid_model_ds/_core/visualizer/callbacks/config.py Co-authored-by: Vincent Koppen <[email protected]> Signed-off-by: Thijs Baaijen <[email protected]> * use constant Signed-off-by: Thijs Baaijen <[email protected]> * use constant for bootstrap class Signed-off-by: Thijs Baaijen <[email protected]> --------- Signed-off-by: Thijs Baaijen <[email protected]> Co-authored-by: Vincent Koppen <[email protected]>
1 parent 38d9969 commit 042052b

File tree

16 files changed

+308
-194
lines changed

16 files changed

+308
-194
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ dev = [
4646
"pre-commit>=4",
4747
# Visualization (make sure these stay equalivalent to the 'visualizer' group)
4848
"dash>=3.0.0",
49-
"dash-bootstrap-components>=2.0.0",
49+
"dash-bootstrap-components>=2.0.3",
5050
"dash-cytoscape>=1.0.2",
5151
]
5252

src/power_grid_model_ds/_core/visualizer/app.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88

99
from power_grid_model_ds._core.model.grids.base import Grid
1010
from power_grid_model_ds._core.visualizer.callbacks import ( # noqa: F401 # pylint: disable=unused-import
11-
element_scaling,
11+
config,
1212
element_selection,
13-
layout_dropdown,
13+
header,
1414
search_form,
1515
)
1616
from power_grid_model_ds._core.visualizer.layout.cytoscape_html import get_cytoscape_html
17+
from power_grid_model_ds._core.visualizer.layout.cytoscape_styling import DEFAULT_STYLESHEET
1718
from power_grid_model_ds._core.visualizer.layout.header import HEADER_HTML
1819
from power_grid_model_ds._core.visualizer.layout.selection_output import SELECTION_OUTPUT_HTML
1920
from power_grid_model_ds._core.visualizer.parsers import parse_branches, parse_node_array
@@ -76,6 +77,7 @@ def get_app_layout(grid: Grid) -> html.Div:
7677
return html.Div(
7778
[
7879
columns_store,
80+
dcc.Store(id="stylesheet-store", data=DEFAULT_STYLESHEET),
7981
HEADER_HTML,
8082
html.Hr(style={"border-color": "white", "margin": "0"}),
8183
cytoscape_html,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]>
2+
#
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
6+
from dash import Input, Output, State, callback
7+
from dash.exceptions import PreventUpdate
8+
9+
from power_grid_model_ds._core.visualizer.layout.cytoscape_styling import BRANCH_WIDTH, NODE_SIZE
10+
from power_grid_model_ds._core.visualizer.typing import STYLESHEET
11+
12+
13+
@callback(
14+
Output("stylesheet-store", "data", allow_duplicate=True),
15+
Output("cytoscape-graph", "stylesheet", allow_duplicate=True),
16+
Input("node-scale-input", "value"),
17+
Input("edge-scale-input", "value"),
18+
State("stylesheet-store", "data"),
19+
prevent_initial_call=True,
20+
)
21+
def scale_elements(node_scale: float, edge_scale: float, stylesheet: STYLESHEET) -> tuple[STYLESHEET, STYLESHEET]:
22+
"""Callback to scale the elements of the graph."""
23+
if stylesheet is None:
24+
raise PreventUpdate
25+
if node_scale == 1 and edge_scale == 1:
26+
raise PreventUpdate
27+
new_stylesheet = stylesheet.copy()
28+
edge_style = {
29+
"selector": "edge",
30+
"style": {
31+
"width": BRANCH_WIDTH * edge_scale,
32+
},
33+
}
34+
new_stylesheet.append(edge_style)
35+
node_style = {
36+
"selector": "node",
37+
"style": {
38+
"height": NODE_SIZE * node_scale,
39+
"width": NODE_SIZE * node_scale,
40+
},
41+
}
42+
new_stylesheet.append(node_style)
43+
44+
return new_stylesheet, new_stylesheet
45+
46+
47+
@callback(Output("cytoscape-graph", "layout"), Input("dropdown-update-layout", "value"), prevent_initial_call=True)
48+
def update_layout(layout):
49+
"""Callback to update the layout of the graph."""
50+
return {"name": layout, "animate": True}
51+
52+
53+
@callback(
54+
Output("cytoscape-graph", "stylesheet", allow_duplicate=True),
55+
Input("show-arrows", "value"),
56+
State("cytoscape-graph", "stylesheet"),
57+
prevent_initial_call=True,
58+
)
59+
def update_arrows(show_arrows, current_stylesheet):
60+
"""Callback to update the arrow style of edges in the graph."""
61+
selectors = [rule["selector"] for rule in current_stylesheet]
62+
index = selectors.index("edge")
63+
edge_style = current_stylesheet[index]["style"]
64+
65+
edge_style["target-arrow-shape"] = "triangle" if show_arrows else "none"
66+
return current_stylesheet

src/power_grid_model_ds/_core/visualizer/callbacks/element_scaling.py

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]>
2+
#
3+
# SPDX-License-Identifier: MPL-2.0
4+
5+
import dash
6+
from dash import Input, Output, callback
7+
8+
from power_grid_model_ds._core.visualizer.layout.header import CONFIG_DIV, LEGENDA_DIV, SEARCH_DIV
9+
10+
11+
@callback(
12+
Output("header-right-col", "children"),
13+
[
14+
Input("btn-legend", "n_clicks"),
15+
Input("btn-search", "n_clicks"),
16+
Input("btn-config", "n_clicks"),
17+
],
18+
)
19+
def update_right_col(_btn1, _btn2, _btn3):
20+
"""Update the right column content based on the button clicked."""
21+
ctx = dash.callback_context
22+
if not ctx.triggered:
23+
return LEGENDA_DIV
24+
button_id = ctx.triggered[0]["prop_id"].split(".")[0]
25+
button_map = {
26+
"btn-legend": LEGENDA_DIV,
27+
"btn-search": SEARCH_DIV,
28+
"btn-config": CONFIG_DIV,
29+
}
30+
return button_map.get(button_id, "Right Column")

src/power_grid_model_ds/_core/visualizer/callbacks/layout_dropdown.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/power_grid_model_ds/_core/visualizer/callbacks/search_form.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]>
22
#
33
# SPDX-License-Identifier: MPL-2.0
4-
from typing import Any
54

6-
from dash import Input, Output, callback
5+
from dash import Input, Output, State, callback
6+
from dash.exceptions import PreventUpdate
77

88
from power_grid_model_ds._core.visualizer.layout.colors import CYTO_COLORS
9-
from power_grid_model_ds._core.visualizer.layout.cytoscape_styling import DEFAULT_STYLESHEET
9+
from power_grid_model_ds._core.visualizer.typing import STYLESHEET
1010

1111

1212
@callback(
@@ -15,11 +15,12 @@
1515
Input("search-form-column-input", "value"),
1616
Input("search-form-operator-input", "value"),
1717
Input("search-form-value-input", "value"),
18+
State("stylesheet-store", "data"),
1819
)
19-
def search_element(group: str, column: str, operator: str, value: str) -> list[dict[str, Any]]:
20+
def search_element(group: str, column: str, operator: str, value: str, stylesheet: STYLESHEET) -> STYLESHEET:
2021
"""Color the specified element red based on the input values."""
2122
if not group or not column or not value:
22-
return DEFAULT_STYLESHEET
23+
raise PreventUpdate
2324

2425
# Determine if we're working with a node or an edge type
2526
if group == "node":
@@ -39,7 +40,8 @@ def search_element(group: str, column: str, operator: str, value: str) -> list[d
3940
"selector": selector,
4041
"style": style,
4142
}
42-
return DEFAULT_STYLESHEET + [new_style]
43+
updated_stylesheet = stylesheet + [new_style]
44+
return updated_stylesheet
4345

4446

4547
@callback(

src/power_grid_model_ds/_core/visualizer/layout/cytoscape_config.py

Lines changed: 0 additions & 54 deletions
This file was deleted.

src/power_grid_model_ds/_core/visualizer/layout/cytoscape_html.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
from power_grid_model_ds._core.visualizer.layout.colors import BACKGROUND_COLOR
1111
from power_grid_model_ds._core.visualizer.layout.cytoscape_styling import DEFAULT_STYLESHEET
1212

13-
LAYOUT_OPTIONS = ["random", "circle", "concentric", "grid", "cose", "breadthfirst"]
14-
1513
_CYTO_INNER_STYLE = {"width": "100%", "height": "100%", "background-color": BACKGROUND_COLOR}
1614
_CYTO_OUTER_STYLE = {"height": "80vh"}
1715

src/power_grid_model_ds/_core/visualizer/layout/header.py

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,60 @@
33
# SPDX-License-Identifier: MPL-2.0
44

55
import dash_bootstrap_components as dbc
6+
from dash import html
67

7-
from power_grid_model_ds._core.visualizer.layout.colors import BACKGROUND_COLOR
8-
from power_grid_model_ds._core.visualizer.layout.cytoscape_config import LAYOUT_DROPDOWN_HTML, SCALE_INPUTS
9-
from power_grid_model_ds._core.visualizer.layout.legenda import LEGENDA_HTML
10-
from power_grid_model_ds._core.visualizer.layout.search_form import SEARCH_FORM_HTML
11-
12-
_SEARCH_FORM_CARD_STYLE = {
13-
"background-color": "#555555",
14-
"color": "white",
15-
"border-left": "1px solid white",
16-
"border-right": "1px solid white",
17-
"border-radius": 0,
8+
from power_grid_model_ds._core.visualizer.layout.header_config import CONFIG_ELEMENTS
9+
from power_grid_model_ds._core.visualizer.layout.header_legenda import LEGENDA_ELEMENTS, LEGENDA_STYLE
10+
from power_grid_model_ds._core.visualizer.layout.header_search import SEARCH_ELEMENTS
11+
12+
_MENU_BUTTON_STYLE_CLASS = "me-2 btn-outline-primary"
13+
14+
_LEFT_COLUMN_HTML = dbc.Col(
15+
[
16+
dbc.Button("Legend", id="btn-legend", className=_MENU_BUTTON_STYLE_CLASS),
17+
dbc.Button("Search", id="btn-search", className=_MENU_BUTTON_STYLE_CLASS),
18+
dbc.Button("Config", id="btn-config", className=_MENU_BUTTON_STYLE_CLASS),
19+
],
20+
id="header-left-col",
21+
width=5,
22+
style={
23+
"display": "flex",
24+
"align-items": "center",
25+
"justify-content": "center",
26+
"border-right": "1px solid white",
27+
},
28+
)
29+
30+
31+
_RIGHT_COLUMN_STYLE = {
32+
"display": "flex",
33+
"align-items": "center",
34+
"width": "100%",
1835
}
1936

2037

38+
CONFIG_DIV = html.Div(CONFIG_ELEMENTS, style=_RIGHT_COLUMN_STYLE | {"justify-content": "space-between"})
39+
SEARCH_DIV = html.Div(SEARCH_ELEMENTS, style=_RIGHT_COLUMN_STYLE | {"justify-content": "center"})
40+
LEGENDA_DIV = html.Div(LEGENDA_ELEMENTS, style=_RIGHT_COLUMN_STYLE | LEGENDA_STYLE)
41+
42+
_RIGHT_COLUMN_HTML = dbc.Col(
43+
[LEGENDA_DIV, SEARCH_DIV, CONFIG_DIV],
44+
id="header-right-col",
45+
width=7,
46+
)
47+
2148
HEADER_HTML = dbc.Row(
2249
[
23-
dbc.Col(LEGENDA_HTML, className="d-flex align-items-center"),
24-
dbc.Col(
25-
dbc.Card(SEARCH_FORM_HTML, style=_SEARCH_FORM_CARD_STYLE),
26-
className="d-flex justify-content-center align-items-center",
27-
),
28-
dbc.Col(SCALE_INPUTS + LAYOUT_DROPDOWN_HTML, className="d-flex justify-content-end align-items-center"),
50+
_LEFT_COLUMN_HTML,
51+
_RIGHT_COLUMN_HTML,
2952
],
30-
style={"background-color": BACKGROUND_COLOR},
53+
style={
54+
"background-color": "#343a40",
55+
"color": "white",
56+
"padding": "1rem 0",
57+
"margin": 0,
58+
"height": "90px",
59+
"width": "100%",
60+
},
61+
align="center",
3162
)

0 commit comments

Comments
 (0)