Skip to content

Commit 5b5ede4

Browse files
feat: new interactive graph visualization (#609)
## Description Adds a new lightweight way to visualize graphs interactively. No added dependencies (the javascript code runs entirely in the broswer and sources libraries via CDN). The way this is implemented is quite simple: the data is embedded directly in the HTML in json format using a jinja template. Then javascript code does the rest (yes, definitely with the help of coding assistants). The largest graph on which this was tested was a cutout graph with n320 grid globally and a 2km grid over central Europe (the one we currently have at MeteoSwiss). Loaded in about 6s and then still runs relatively smoothly. Still, we might be reaching a limit with >1'500'000 nodes with lots of connections - we might need to find some optimizations to the code at some point as I am sure there are some left. Nothing changes in terms of interface - this will simply add another output to the command: ```shell anemoi-graphs inspect graph.pt output_dir/ ``` ## Features - fast GPU-based rendering - full 3D scene navigation - interactive controls for nodes and edges (toggles, radius scaling, colors, etc.) - nodes selection and highlighting - globe map - only for nodes coordinates and edges (no attributes values, for now) ## Graphs tested - [x] n320 global (+ hidden) - [x] CERRA LAM graph from RMI (+ hidden) - [x] COSMO SGM graph from MeteoSwiss (+hidden) - [x] n320 global hierarchical (+ multiple hidden levels) ---- <img width="1599" height="900" alt="image" src="https://github.com/user-attachments/assets/83af87bb-7753-4243-9619-2825e78a62bc" /> <!-- readthedocs-preview anemoi-training start --> ---- 📚 Documentation preview 📚: https://anemoi-training--609.org.readthedocs.build/en/609/ <!-- readthedocs-preview anemoi-training end --> <!-- readthedocs-preview anemoi-graphs start --> ---- 📚 Documentation preview 📚: https://anemoi-graphs--609.org.readthedocs.build/en/609/ <!-- readthedocs-preview anemoi-graphs end --> <!-- readthedocs-preview anemoi-models start --> ---- 📚 Documentation preview 📚: https://anemoi-models--609.org.readthedocs.build/en/609/ <!-- readthedocs-preview anemoi-models end --> --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 8659de9 commit 5b5ede4

File tree

4 files changed

+1102
-10
lines changed

4 files changed

+1102
-10
lines changed

graphs/src/anemoi/graphs/inspect.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
from anemoi.graphs.plotting.displots import plot_distribution_edge_attributes
1717
from anemoi.graphs.plotting.displots import plot_distribution_node_attributes
1818
from anemoi.graphs.plotting.displots import plot_distribution_node_derived_attributes
19-
from anemoi.graphs.plotting.interactive_html import plot_interactive_nodes
20-
from anemoi.graphs.plotting.interactive_html import plot_interactive_subgraph
21-
from anemoi.graphs.plotting.interactive_html import plot_isolated_nodes
19+
from anemoi.graphs.plotting.interactive_2d_html import plot_interactive_nodes_2d
20+
from anemoi.graphs.plotting.interactive_2d_html import plot_interactive_subgraph_2d
21+
from anemoi.graphs.plotting.interactive_2d_html import plot_isolated_nodes_2d
22+
from anemoi.graphs.plotting.interactive_3d_html import plot_interactive_graph_3d
2223
from anemoi.graphs.processors.post_process import SubsetNodesInArea
2324

2425
LOGGER = logging.getLogger(__name__)
@@ -68,13 +69,17 @@ def __init__(
6869

6970
def inspect(self):
7071
"""Run all the inspector methods."""
72+
73+
LOGGER.info("Saving interactive 3d plot of the graph...")
74+
plot_interactive_graph_3d(self.graph, out_file=self.output_path / "graph.html")
75+
7176
LOGGER.info("Saving interactive plots of isolated nodes ...")
72-
plot_isolated_nodes(self.graph, self.output_path / "isolated_nodes.html")
77+
plot_isolated_nodes_2d(self.graph, self.output_path / "isolated_nodes.html")
7378

7479
LOGGER.info("Saving interactive plots of subgraphs ...")
7580
for edges_subgraph in self.graph.edge_types:
7681
ofile = self.output_path / f"{edges_subgraph[0]}_to_{edges_subgraph[2]}.html"
77-
plot_interactive_subgraph(self.graph, edges_subgraph, out_file=ofile)
82+
plot_interactive_subgraph_2d(self.graph, edges_subgraph, out_file=ofile)
7883

7984
if self.show_attribute_distributions:
8085
LOGGER.info("Saving distribution plots of node ande edge attributes ...")
@@ -85,4 +90,6 @@ def inspect(self):
8590
if self.show_nodes:
8691
LOGGER.info("Saving interactive plots of nodes ...")
8792
for nodes_name in self.graph.node_types:
88-
plot_interactive_nodes(self.graph, nodes_name, out_file=self.output_path / f"{nodes_name}_nodes.html")
93+
plot_interactive_nodes_2d(
94+
self.graph, nodes_name, out_file=self.output_path / f"{nodes_name}_nodes.html"
95+
)

graphs/src/anemoi/graphs/plotting/interactive_html.py renamed to graphs/src/anemoi/graphs/plotting/interactive_2d_html.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import logging
1111
from pathlib import Path
12+
from typing import Optional
13+
from typing import Union
1214

1315
import matplotlib.pyplot as plt
1416
import numpy as np
@@ -28,10 +30,10 @@
2830
LOGGER = logging.getLogger(__name__)
2931

3032

31-
def plot_interactive_subgraph(
33+
def plot_interactive_subgraph_2d(
3234
graph: HeteroData,
3335
edges_to_plot: tuple[str, str, str],
34-
out_file: str | Path | None = None,
36+
out_file: Optional[Union[str, Path]] = None,
3537
) -> None:
3638
"""Plots a bipartite graph (bi-graph).
3739
@@ -117,7 +119,7 @@ def plot_interactive_subgraph(
117119
fig.show()
118120

119121

120-
def plot_isolated_nodes(graph: HeteroData, out_file: str | Path | None = None) -> None:
122+
def plot_isolated_nodes_2d(graph: HeteroData, out_file: Optional[Union[str, Path]] = None) -> None:
121123
"""Plot isolated nodes.
122124
123125
This method creates an interactive visualization of the isolated nodes in the graph.
@@ -169,7 +171,7 @@ def plot_isolated_nodes(graph: HeteroData, out_file: str | Path | None = None) -
169171
fig.show()
170172

171173

172-
def plot_interactive_nodes(graph: HeteroData, nodes_name: str, out_file: str | None = None) -> None:
174+
def plot_interactive_nodes_2d(graph: HeteroData, nodes_name: str, out_file: Optional[str] = None) -> None:
173175
"""Plot nodes.
174176
175177
This method creates an interactive visualization of a set of nodes.
@@ -235,6 +237,7 @@ def plot_interactive_nodes(graph: HeteroData, nodes_name: str, out_file: str | N
235237
sliders=[
236238
dict(active=0, currentvalue={"visible": False}, len=0.4, x=0.5, xanchor="center", steps=slider_steps)
237239
],
240+
titlefont_size=16,
238241
showlegend=False,
239242
hovermode="closest",
240243
margin={"b": 20, "l": 5, "r": 5, "t": 40},

0 commit comments

Comments
 (0)