-
Notifications
You must be signed in to change notification settings - Fork 6
Feat/add visualizer #47
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
Merged
Merged
Changes from all commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
8523654
Add visualizer dependencies
Thijss 5426cc8
Add visualizer code
Thijss 11b422a
Add visualize method to public api
Thijss 735e2e4
Fix all imports
Thijss e58a463
Add integration test
Thijss f3041a7
some cleanup (not done)
Thijss 8c460b3
rename test file
Thijss 32502fc
decrease to 80vh
Thijss 0520977
add highlighted color
Thijss fb3aee2
search form on one sentence
Thijss 78fd55f
Improve search form
Thijss b19ffa8
Lots of styling
Thijss ff38bb2
Add scaling, legenda and frontend layout
Thijss dae8686
Adjust scaling steps
Thijss fb7ff04
adjust view height
Thijss 403bd30
cleanup
Thijss d47df1e
remove uv.lock
Thijss 5e8475d
add REUSE Compliance
Thijss 32a00d8
make visualize import optional and
Thijss e5cb443
move visualizer import
Thijss 55389f3
cleanup code
Thijss 40e9761
install visualizer deps in CI
Thijss 10ba51c
Add parser tests
Thijss c498ffc
Add REUSE compliance
Thijss 678a9ee
Install visualizer in CI
Thijss 92597c0
cleanup
Thijss 2cbcd3a
add visualizer dependencies to 'dev' group too
Thijss 422d76f
move CoordinatedNodeArray
Thijss c15a384
Add documentation
Thijss f33bd47
Add port parameter
Thijss 63069de
move vis-deps to correct group
Thijss 1f1f0b0
Add reuse
Thijss c4c509a
rename main.py to app.py
Thijss bf563e4
add layout test
Thijss bff8c2b
exclude visualizer from coverage
Thijss 3bf3f1b
Update visualizer docs with disclaimer
Thijss f26873e
re-enable visualizer tests
Thijss a4e6585
Update docs
Thijss 985da8d
Update pyproject.toml
Thijss cf74704
Add basic test for get_app_layout
Thijss 5d583b2
Merge remote-tracking branch 'origin/feat/add-visualizer' into feat/a…
Thijss e896064
clenaup
Thijss 02c3ba1
add type hints
Thijss be09046
add callback tests
Thijss e254271
use constants
Thijss f815151
bump minor version
Thijss 5318f28
Merge branch 'main' into feat/add-visualizer
Thijss File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| 1.3 | ||
| 1.3 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| <!-- | ||
| SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]> | ||
| SPDX-License-Identifier: MPL-2.0 | ||
| --> | ||
|
|
||
| # Visualizer | ||
|
|
||
| ## Features | ||
|
|
||
| - Based on [dash-cytoscape](https://github.com/plotly/dash-cytoscape). | ||
| - Visualize small and large (10000+ nodes) networks | ||
| - Explore attributes of nodes and branches | ||
| - Highlight specific nodes and branches | ||
| - Visualize various layouts, including hierarchical, force-directed and coordinate-based layouts | ||
|
|
||
| With Coordinates | Hierarchical | Force-Directed | ||
| :------------------:|:------------:|:-------------: | ||
| <img width="250" alt="Coordinates" src="https://github.com/user-attachments/assets/6f991cb1-08b4-4c4b-8adc-eed36f58db40" /> | <img width="250" alt="Hierarchical" src="https://github.com/user-attachments/assets/0cf5684d-fb7c-4920-92b8-1e49bc827a92" /> | <img width="250" alt="Force-Directed" src="https://github.com/user-attachments/assets/f0167ded-ceb4-4a31-a91e-e029dd6d7f13" /> | ||
|
|
||
| ----- | ||
| ## Quickstart | ||
| #### Installation | ||
| ```bash | ||
| pip install 'power-grid-model-ds[visualizer]' # quotes added for zsh compatibility | ||
| ``` | ||
|
|
||
| #### Usage | ||
| ```python | ||
| from power_grid_model_ds import Grid | ||
| from power_grid_model_ds.visualizer import visualize | ||
| from power_grid_model_ds.generators import RadialGridGenerator | ||
|
|
||
| grid = RadialGridGenerator(Grid).run() | ||
| visualize(grid) | ||
| ``` | ||
| This will start a local web server at http://localhost:8050 | ||
|
|
||
| #### Disclaimer | ||
| Please note that the visualizer is still a work in progress and may not be fully functional or contain bugs. | ||
| We welcome any feedback or suggestions for improvement. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]> | ||
| # | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| import dash_bootstrap_components as dbc | ||
| from dash import Dash, dcc, html | ||
| from dash_bootstrap_components.icons import FONT_AWESOME | ||
|
|
||
| from power_grid_model_ds._core.model.grids.base import Grid | ||
| from power_grid_model_ds._core.visualizer.callbacks import ( # noqa: F401 # pylint: disable=unused-import | ||
| element_scaling, | ||
| element_selection, | ||
| layout_dropdown, | ||
| search_form, | ||
| ) | ||
| from power_grid_model_ds._core.visualizer.layout.cytoscape_html import get_cytoscape_html | ||
| from power_grid_model_ds._core.visualizer.layout.header import HEADER_HTML | ||
| from power_grid_model_ds._core.visualizer.layout.selection_output import SELECTION_OUTPUT_HTML | ||
| from power_grid_model_ds._core.visualizer.parsers import parse_branches, parse_node_array | ||
| from power_grid_model_ds.arrays import NodeArray | ||
|
|
||
| GOOGLE_FONTS = "https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" | ||
| MDBOOTSTRAP = "https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/8.2.0/mdb.min.css" | ||
|
|
||
|
|
||
| def visualize(grid: Grid, debug: bool = False, port: int = 8050) -> None: | ||
| """Visualize the Grid. | ||
| grid: Grid | ||
| The grid to visualize. | ||
| layout: str | ||
| The layout to use. | ||
| If 'layout' is not provided (""): | ||
| And grid.node contains "x" and "y" columns: | ||
| The layout will be set to "preset" which uses the x and y coordinates to place the nodes. | ||
| Otherwise: | ||
| The layout will be set to "breadthfirst", which is a hierarchical breadth-first-search (BFS) layout. | ||
| Other options: | ||
| - "random": A layout that places the nodes randomly. | ||
| - "circle": A layout that places the nodes in a circle. | ||
| - "concentric": A layout that places the nodes in concentric circles. | ||
| - "grid": A layout that places the nodes in a grid matrix. | ||
| - "cose": A layout that uses the CompoundSpring Embedder algorithm (force-directed layout) | ||
| """ | ||
|
|
||
| app = Dash( | ||
| external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.BOOTSTRAP, MDBOOTSTRAP, FONT_AWESOME, GOOGLE_FONTS] | ||
| ) | ||
| app.layout = get_app_layout(grid) | ||
| app.run(debug=debug, port=port) | ||
|
|
||
|
|
||
| def _get_columns_store(grid: Grid) -> dcc.Store: | ||
| return dcc.Store( | ||
| id="columns-store", | ||
| data={ | ||
| "node": grid.node.columns, | ||
| "line": grid.line.columns, | ||
| "link": grid.link.columns, | ||
| "transformer": grid.transformer.columns, | ||
| "branch": grid.branches.columns, | ||
| }, | ||
| ) | ||
|
|
||
|
|
||
| def get_app_layout(grid: Grid) -> html.Div: | ||
| """Get the app layout.""" | ||
| columns_store = _get_columns_store(grid) | ||
| graph_layout = _get_graph_layout(grid.node) | ||
| elements = parse_node_array(grid.node) + parse_branches(grid) | ||
| cytoscape_html = get_cytoscape_html(graph_layout, elements) | ||
|
|
||
| return html.Div( | ||
| [ | ||
| columns_store, | ||
| HEADER_HTML, | ||
| html.Hr(style={"border-color": "white", "margin": "0"}), | ||
| cytoscape_html, | ||
| SELECTION_OUTPUT_HTML, | ||
| ], | ||
| ) | ||
|
|
||
|
|
||
| def _get_graph_layout(nodes: NodeArray) -> str: | ||
| """Determine the graph layout""" | ||
| if "x" in nodes.columns and "y" in nodes.columns: | ||
| return "preset" | ||
| return "breadthfirst" |
Empty file.
37 changes: 37 additions & 0 deletions
37
src/power_grid_model_ds/_core/visualizer/callbacks/element_scaling.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]> | ||
| # | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| from copy import deepcopy | ||
| from typing import Any | ||
|
|
||
| from dash import Input, Output, callback | ||
|
|
||
| from power_grid_model_ds._core.visualizer.layout.cytoscape_styling import BRANCH_WIDTH, DEFAULT_STYLESHEET, NODE_SIZE | ||
|
|
||
|
|
||
| @callback( | ||
| Output("cytoscape-graph", "stylesheet", allow_duplicate=True), | ||
| Input("node-scale-input", "value"), | ||
| Input("edge-scale-input", "value"), | ||
| prevent_initial_call=True, | ||
| ) | ||
| def scale_elements(node_scale: float, edge_scale: float) -> list[dict[str, Any]]: | ||
| """Callback to scale the elements of the graph.""" | ||
| new_stylesheet = deepcopy(DEFAULT_STYLESHEET) | ||
| edge_style = { | ||
| "selector": "edge", | ||
| "style": { | ||
| "width": BRANCH_WIDTH * edge_scale, | ||
| }, | ||
| } | ||
| new_stylesheet.append(edge_style) | ||
| node_style = { | ||
| "selector": "node", | ||
| "style": { | ||
| "height": NODE_SIZE * node_scale, | ||
| "width": NODE_SIZE * node_scale, | ||
| }, | ||
| } | ||
| new_stylesheet.append(node_style) | ||
| return new_stylesheet |
33 changes: 33 additions & 0 deletions
33
src/power_grid_model_ds/_core/visualizer/callbacks/element_selection.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]> | ||
| # | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| from typing import Any | ||
|
|
||
| from dash import Input, Output, callback, dash_table | ||
|
|
||
| from power_grid_model_ds._core.visualizer.layout.selection_output import ( | ||
| SELECTION_OUTPUT_HTML, | ||
| ) | ||
|
|
||
|
|
||
| @callback( | ||
| Output("selection-output", "children"), | ||
| Input("cytoscape-graph", "selectedNodeData"), | ||
| Input("cytoscape-graph", "selectedEdgeData"), | ||
| ) | ||
| def display_selected_element(node_data, edge_data): | ||
| """Display the tapped edge data.""" | ||
| if node_data: | ||
| return _to_data_table(node_data.pop()) | ||
| if edge_data: | ||
| return _to_data_table(edge_data.pop()) | ||
| return SELECTION_OUTPUT_HTML | ||
|
|
||
|
|
||
| def _to_data_table(data: dict[str, Any]): | ||
| columns = data.keys() | ||
| data_table = dash_table.DataTable( | ||
| data=[data], columns=[{"name": key, "id": key} for key in columns], editable=False | ||
| ) | ||
| return data_table |
11 changes: 11 additions & 0 deletions
11
src/power_grid_model_ds/_core/visualizer/callbacks/layout_dropdown.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]> | ||
| # | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| from dash import Input, Output, callback | ||
|
|
||
|
|
||
| @callback(Output("cytoscape-graph", "layout"), Input("dropdown-update-layout", "value"), prevent_initial_call=True) | ||
| def update_layout(layout): | ||
| """Callback to update the layout of the graph.""" | ||
| return {"name": layout, "animate": True} |
60 changes: 60 additions & 0 deletions
60
src/power_grid_model_ds/_core/visualizer/callbacks/search_form.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]> | ||
| # | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| from typing import Any | ||
|
|
||
| from dash import Input, Output, callback | ||
|
|
||
| from power_grid_model_ds._core.visualizer.layout.colors import CYTO_COLORS | ||
| from power_grid_model_ds._core.visualizer.layout.cytoscape_styling import DEFAULT_STYLESHEET | ||
|
|
||
|
|
||
| @callback( | ||
| Output("cytoscape-graph", "stylesheet"), | ||
| Input("search-form-group-input", "value"), | ||
| Input("search-form-column-input", "value"), | ||
| Input("search-form-operator-input", "value"), | ||
| Input("search-form-value-input", "value"), | ||
| ) | ||
| def search_element(group: str, column: str, operator: str, value: str) -> list[dict[str, Any]]: | ||
| """Color the specified element red based on the input values.""" | ||
| if not group or not column or not value: | ||
| return DEFAULT_STYLESHEET | ||
|
|
||
| # Determine if we're working with a node or an edge type | ||
| if group == "node": | ||
| style = { | ||
| "background-color": CYTO_COLORS["highlighted"], | ||
| "text-background-color": CYTO_COLORS["highlighted"], | ||
| } | ||
| else: | ||
| style = {"line-color": CYTO_COLORS["highlighted"], "target-arrow-color": CYTO_COLORS["highlighted"]} | ||
|
|
||
| if column == "id": | ||
| selector = f'[{column} {operator} "{value}"]' | ||
| else: | ||
| selector = f"[{column} {operator} {value}]" | ||
|
|
||
| new_style = { | ||
| "selector": selector, | ||
| "style": style, | ||
| } | ||
| return DEFAULT_STYLESHEET + [new_style] | ||
|
|
||
|
|
||
| @callback( | ||
| Output("search-form-column-input", "options"), | ||
| Output("search-form-column-input", "value"), | ||
| Input("search-form-group-input", "value"), | ||
| Input("columns-store", "data"), | ||
| ) | ||
| def update_column_options(selected_group, store_data): | ||
| """Update the column dropdown options based on the selected group.""" | ||
| if not selected_group or not store_data: | ||
| return [], None | ||
|
|
||
| # Get columns for the selected group (node, line, link, or transformer) | ||
| columns = store_data.get(selected_group, []) | ||
| default_value = columns[0] if columns else "id" | ||
|
|
||
| return columns, default_value |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]> | ||
| # | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| YELLOW = "#facc37" | ||
| CYTO_COLORS = { | ||
| "line": YELLOW, | ||
| "link": "green", | ||
| "transformer": "#4290f5", | ||
| "node": YELLOW, | ||
| "selected": "#e28743", | ||
| "selected_transformer": "#0349a3", | ||
| "substation_node": "purple", | ||
| "open_branch": "#c9c9c9", | ||
| "highlighted": "#a10000", | ||
| } | ||
| BACKGROUND_COLOR = "#555555" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.