Skip to content

feat: add 3D interactive graph visualisation with Plotly#118

Open
yuvraajnarula wants to merge 9 commits intomllam:mainfrom
yuvraajnarula:feature/3d-visualization
Open

feat: add 3D interactive graph visualisation with Plotly#118
yuvraajnarula wants to merge 9 commits intomllam:mainfrom
yuvraajnarula:feature/3d-visualization

Conversation

@yuvraajnarula
Copy link
Copy Markdown

Describe your changes

Adds weather_model_graphs.visualise.plot_3d with a single public entry point:

render_with_plotly(graph: nx.DiGraph, *, show=True, title=None,
                   node_size=4, edge_width=1.5,
                   component_colors=None) -> go.Figure

Node positions are read from the existing pos node attribute (x, y). The vertical axis (z) is derived from the integer level node attribute present on mesh nodes in hierarchical and multiscale graphs. Grid nodes that carry no level and are placed at z = -1 so they always appear below all mesh levels, matching the convention agreed in #20.

All edges of the same component (g2m, m2m, m2g) are batched into a single Scatter3d trace with None separators between segments. This is the key performance fix discussed in #20: instead of one trace per edge (which produces >100 MB HTML files for global graphs), the entire component is one trace object, keeping output small and browser-friendly even for dense graphs.

Node kinds (grid, mesh level 0, mesh level 1, …) are split into separate traces so the user gets independent legend toggles per level in the interactive figure.

plotly is guarded as an optional dependency, and the rest of weather_model_graphs can be imported without it. The function raises a clear ImportError with install instructions if plotly is absent, following the same pattern as the existing HAS_PYG guard in save.py.

Two small edits are required in existing files:

  1. src/weather_model_graphs/visualise/__init__.py — expose the new function:
    from .plot_3d import render_with_plotly
  2. pyproject.toml — add plotly to an optional dependency group:
    [project.optional-dependencies]
    visualisation = ["plotly"]

Dependencies:

  • plotly (install with pip install weather-model-graphs[visualisation])
  • numpy, networkx (already in core dependencies)

Issue Link

Closes #20

Type of change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📖 Documentation (Addition or improvements to documentation)

Checklist before requesting a review

  • My branch is up-to-date with the target branch - if not update your fork with the changes from the target branch (use pull with --rebase option if possible).
  • I have performed a self-review of my code
  • For any new/modified functions/classes I have added docstrings that clearly describe its purpose, expected inputs and returned values
  • I have placed in-line comments to clarify the intent of any hard-to-understand passages of my code
  • I have updated the documentation to cover introduced code changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have given the PR a name that clearly describes the change, written in imperative form (context).
  • I have requested a reviewer and an assignee (assignee is responsible for merging)

Checklist for reviewers

Each PR comes with its own improvements and flaws. The reviewer should check the following:

  • the code is readable
  • the code is well tested
  • the code is documented (including return types and parameters)
  • the code is easy to maintain

Author checklist after completed review

  • I have added a line to the CHANGELOG describing this change, in a section
    reflecting type of change (add section where missing):
    • added: when you have added new functionality
    • changed: when default behaviour of the code has been changed
    • fixes: when your contribution fixes a bug

Checklist for assignee

  • PR is up to date with the base branch
  • the tests pass
  • author has added an entry to the changelog (and designated the change as added, changed or fixed)
  • Once the PR is ready to be merged, squash commits and merge the PR.

@yuvraajnarula
Copy link
Copy Markdown
Author

Here's a simple script usage with icosahedral.py and plot_3D.py:

flat_mesh = create_flat_icosahedral_mesh_graph(subdivisions=2)

nx.set_edge_attributes(flat_mesh, "m2m", name="component")

fig_flat = render_with_plotly(
    flat_mesh,
    show=True,                        
    title="Flat Icosahedral Mesh (subdivisions=2)",
    node_size=5.0,
    edge_width=1.0,
    component_colors={"m2m": "#43A047"},
)
hier_mesh = create_hierarchical_icosahedral_mesh_graph(max_subdivisions=3)

for u, v, attrs in hier_mesh.edges(data=True):
    edge_level = attrs.get("level", "")
    if isinstance(edge_level, str) and "_to_" in edge_level:
        parts = edge_level.split("_to_")
        comp = "mesh_up" if int(parts[0]) > int(parts[1]) else "mesh_down"
    else:
        comp = "m2m"
    hier_mesh[u][v]["component"] = comp

custom_colors = {
    **DEFAULT_COMPONENT_COLORS,
    "m2m":       "#1565C0"
    "mesh_up":   "#E53935",  
    "mesh_down": "#43A047",   
}

fig_hier = render_with_plotly(
    hier_mesh,
    show=True,
    title="Hierarchical Icosahedral Mesh (max_subdivisions=3)",
    node_size=4.0,
    edge_width=1.2,
    component_colors=custom_colors,
)
fig_flat.write_html("flat_icosahedral.html")
fig_hier.write_html("hierarchical_icosahedral.html")
print("Saved flat_icosahedral.html and hierarchical_icosahedral.html")

@yuvraajnarula
Copy link
Copy Markdown
Author

image image

@leifdenby @joeloskarsson
Let me know what you guys think, I haven't tried to go overboard with #76 3d globe based visualizations just kept it minimal with context to #20

@yuvraajnarula yuvraajnarula marked this pull request as ready for review March 26, 2026 06:31
@Joltsy10
Copy link
Copy Markdown
Contributor

I think the flat representation for just the individual meshes are fine for the most part but I dont think its going overboard with the globe based visualization. At the least a spherical representation would be much better to understand the graph.

Especially for the hierarchical flat graph its not that readable yeah? What I think is happening in the hierarchical one is that the coarser graphs are getting hidden beneath the finer graphs so theyre getting fully hidden, maybe trying to have those above would make it more readable? Overall for hierarchical having a concentric spherical representation is much better if not a proper globe one right now

@yuvraajnarula
Copy link
Copy Markdown
Author

@Joltsy10
Thanks again for the thoughtful feedback on the 3D visualisation!

You were absolutely right: the flat layout (x=lon, y=lat, z=level) was hiding coarser mesh levels in hierarchical graphs. To fix that, I’ve added a concentric spherical layout as an option in plot_3d.py.

Now you can call render_with_plotly(graph, layout="concentric") and each mesh level automatically gets its own sphere radius (base + level × step). Grid nodes sit on a slightly smaller inner sphere, so everything stays visible and the hierarchy is crystal clear. The original flat layout is still the default, so existing code remains unchanged.

I also kept all the performance tricks you liked (edge batching, None separators, and optional plotly dependency). So, the concentric version stays just as browser-friendly.

If you have a chance to test it on your graphs or have ideas for further improvements (e.g., textured Earth or different colour schemes), I’d love to hear them. Happy to collaborate on polishing it further.

Thanks again for pushing this in the right direction. I really appreciate your input!

This is the output I got while I am trying to work more on the 3d globe approach before pushing it.

I hope @leifdenby considers this under the domain.
image

@yuvraajnarula
Copy link
Copy Markdown
Author

usage :

fig_hier_concentric = render_with_plotly(
    hier_mesh,
    show=True,
    title="Hierarchical Icosahedral Mesh (concentric layout)",
    node_size=4.0,
    edge_width=1.2,
    component_colors=custom_colors,
    layout="concentric",
    sphere_base_radius=1.0,
    sphere_radius_step=0.5,
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3D plots for graphs

2 participants