Module: plot
File: src/spatial_graph_algorithms/plot/
Status: Stable.
Purpose
Visualise SpatialGraph objects. All functions return a matplotlib.figure.Figure
and optionally save it to disk. No algorithm logic lives here — only rendering.
Functions
| Function | Input requirements | What it shows |
|---|---|---|
plot_network |
2-D positions |
Nodes + edges; false edges in solid red |
plot_network_3d |
3-D positions |
3-D scatter + edges with adaptive sampling |
plot_edge_length_histogram |
positions + edges |
Distribution of Euclidean edge lengths |
plot_comparison |
positions + reconstructed_positions (2-D) |
Original vs reconstructed side-by-side |
plot_provenance_comparison |
positions + reconstructed_positions (2-D) |
Original vs reconstructed with spatial pattern overlay |
render_simulation_visualization_bundle |
SpatialGraph |
Saves network + histogram to disk |
plot_comparison in Detail
This is the most complex plot. It:
- Applies Procrustes alignment (
scipy.spatial.procrustes) to the reconstructed positions. This standardizes both arrays to unit Frobenius norm, then finds optimal rotation + reflection, so orientation differences do not obscure quality. - Colours nodes by angle from centroid in the original space. The same colour in both panels means the same node — so a good reconstruction shows the same colour gradient.
- Uses the same colour range on both panels for a fair comparison.
The Procrustes alignment is applied only for visualisation — it is not applied to
sg.reconstructed_positions stored in the object.
plot_provenance_comparison in Detail
This function assigns a color to every node based on where it sits in the ground-truth coordinate space, then renders that same color in both panels.
Available patterns (pass as pattern=):
kind |
What it shows | Key params |
|---|---|---|
"grid" (default) |
nx×ny colored spatial grid — like a colored checkerboard | grid_size, cmap |
"checkerboard" |
Classic two-color alternating grid | tile_count, color_a, color_b |
"rings" |
Concentric color rings from centroid | n_rings, cmap |
"quadrants" |
Angular color wedges from centroid | n_wedges, cmap |
"gradient" |
Smooth axis-aligned color gradient | direction, cmap |
"image" |
RGB colors sampled from a user-supplied PNG | image_path |
Key design decision: colors are always derived from sg.positions (ground truth) only,
never from reconstructed_positions. This means node i has the same color in both panels
regardless of where the reconstruction placed it. A good reconstruction shows the pattern
intact; a poor one scrambles or distorts it.
Design Decisions
How are large graph network plots kept readable?
plot_network and plot_network_3d use adaptive defaults based on edge count:
small and medium graphs draw all edges, while larger graphs sample edges in
edge_display="auto" mode. Node size and opacity are reduced as the graph
gets larger, and dense plots are rasterized so saved files stay usable. False
edges are preserved before true edges are sampled so noisy-edge diagnostics
remain visible. Pass edge_display="all" to force every edge,
edge_display="sample" with max_edges=... for an explicit budget, or
edge_display="none" for a node-only view.
Why are false edges solid red? False edges are diagnostic overlays, so they are drawn after true edges with a solid red stroke. Their opacity is high when only a few false edges are drawn and automatically reduced when many are visible, which avoids turning noisy medium graphs into solid red overlays.
Why does DEFAULT_VISUALIZATION_DIR point to .planning/artifacts/visualizations?
Historical — that was the GSD planning artefact path. For user-facing output, pass your
own output_dir argument. This default may change in a future version.
Why return Figure instead of showing it?
Returning the figure lets callers decide whether to display (plt.show()), save
(fig.savefig()), or embed in a notebook. Functions that call plt.show() inside
are harder to test and less flexible.
Why not use seaborn or plotly? matplotlib is already a core dependency. Adding seaborn or plotly for this module would add installation overhead for optional visualisation niceties.
How to Add a New Plot
- Add a function in
plot/network.pywith this signature pattern: - Export it in
plot/__init__.pyand add to__all__. - Add a test that asserts the return type is
matplotlib.figure.Figure. - Close the figure in tests:
import matplotlib.pyplot as plt; plt.close(fig).
Rules:
- Always validate inputs at the top and raise ValueError with a clear message.
- Never call plt.show() inside a plot function.
- Never call plt.close() inside a plot function — let the caller manage figure lifecycle.
- Always use dpi=300 for saved outputs.