Skip to content

Feature: Interactive Graph Visualization Tool #388#439

Open
Kitsunp wants to merge 17 commits intoTeamGraphix:masterfrom
Kitsunp:feat/visualization-issue-388
Open

Feature: Interactive Graph Visualization Tool #388#439
Kitsunp wants to merge 17 commits intoTeamGraphix:masterfrom
Kitsunp:feat/visualization-issue-388

Conversation

@Kitsunp
Copy link

@Kitsunp Kitsunp commented Feb 17, 2026

Description

Implements an interactive visualization tool for MBQC patterns (Issue #388). This tool allows users to manually step through the command sequence, visualizing the evolving graph state, Pauli corrections, and measurement outcomes.

Key Changes

Core Implementation

  • graphix/visualization_interactive.py: Added InteractiveGraphVisualizer with:
    • Step-by-step navigation (buttons, slider, keyboard arrow keys).
    • Real-time graph state updates (active/measured nodes, Pauli corrections).
    • Optional StatevectorBackend integration for live measurement simulation.

Refactoring

  • graphix/visualization.py: Extracted graph layout logic into a reusable get_layout method perfect for sharing between static and interactive visualizers.

Testing & Examples

  • Tests: Added tests/test_visualization_interactive.py with comprehensive logic coverage (mocking matplotlib for CI compatibility).
  • Examples: Added examples/interactive_viz_demo.py (simple) and examples/interactive_viz_qaoa.py (complex).
image

Verification

  • Tests: pytest passes (including new tests).
  • Linting: Passed ruff and mypy strict type checking.
  • Manual: Interactive features verified with example scripts.

Checklist

  • Code follows project style (ruff).
  • Type hints verified (mypy).
  • Tests added and passing.
  • Documentation and examples included.

@codecov
Copy link

codecov bot commented Feb 17, 2026

Codecov Report

❌ Patch coverage is 92.60563% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.93%. Comparing base (03a89c9) to head (4152dcb).

Files with missing lines Patch % Lines
graphix/visualization_interactive.py 93.49% 16 Missing ⚠️
graphix/visualization.py 86.84% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #439      +/-   ##
==========================================
+ Coverage   88.76%   88.93%   +0.17%     
==========================================
  Files          44       45       +1     
  Lines        6308     6588     +280     
==========================================
+ Hits         5599     5859     +260     
- Misses        709      729      +20     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@emlynsg
Copy link
Contributor

emlynsg commented Feb 17, 2026

Hi @Kitsunp , thank you for your work. Good first attempt!

It wasn't stated explicitly in the issue, but we expect in general that contributions are as easy to maintain as possible. In this case, that means we expect that, as much as possible, code relevant to both the visualization and visualization_interactive modules is shared (e.g. defined in the former, and imported in the latter). For example:

  • in setting up the layout of the plots
  • the visual style of the different elements of the plots
  • printing of the pattern elements (see pretty-print, and check the output yourself)
    We are very happy for you to suggest smart ways to break up the functions in the visualization module (as you have started doing) if it makes them easier to use in your interactive module.

A few other points of constructive criticism:

  • the layout is not very efficient or attractive (i.e. there is a lot of whitespace between the plot, pattern on the left and slider on the bottom). There may be a better way of arranging things to make the pattern display more similar to the static plot style (and focusing on importing more from the visualization module will go a long way to helping with that)
  • the display of measurement results is confusing (e.g. in the basic demo file, after measurement node 0 shows as 0 = 1). Please find a way to display the result which is distinct from the node number.

Since we need to approve the Github CI to run, I would recommend you run nox locally to check for errors more quickly before pushing changes.

Feel free to ask clarifying questions, or have another go and @ me when you are ready for another review.

@Kitsunp
Copy link
Author

Kitsunp commented Feb 17, 2026

Hi! I noticed an issue in the dependency workflow that is causing the CI to fail. I've run the tests locally with nox and they pass, so I believe there might be an error in the CI environment (specifically with the veriphix reverse dependency).

However, I am already looking into the points you mentioned. I'm working on sharing more code between the visualization modules, improving the layout to reduce whitespace, and making the measurement results more distinct from the node labels. I will update the PR once I’ve addressed these improvements. Thanks for the feedback!"

@Kitsunp
Copy link
Author

Kitsunp commented Feb 18, 2026

Clarifying questions — PR #439

Hi @emlynsg, thank you for the detailed feedback. I've addressed the layout, measurement display, and started breaking up GraphVisualizer to expose reusable drawing methods. Before going further I have a few design questions:


1. How far should the API of GraphVisualizer be extended?

I've exposed draw_nodes_role(ax, pos), draw_edges(ax, pos) and draw_node_labels(ax, pos) with an injected Axes. The interactive visualizer needs dynamic per-step colouring (grey = measured, red border = active, blue text = corrected), which the current static methods don't cover. Would you be open to adding optional per-node colour overrides?

def draw_nodes_role(
    ax: Axes,
    pos: Mapping[int, _Point],
    show_pauli_measurement: bool = False,
    node_facecolors: Mapping[int, str] | None = None,
    node_edgecolors: Mapping[int, str] | None = None,
) -> None: ...

Similarly for draw_edges, an edge_subset parameter would let the interactive visualizer draw only the edges between currently active nodes.


2. Node labels with extra content

draw_node_labels currently draws only the node number. The interactive visualizer appends measurement results and correction flags. Would you prefer:

  • a) An extra_labels: Mapping[int, str] | None parameter on draw_node_labels.
  • b) Exposing only the fontsize calculation (get_label_fontsize(max_node: int) -> int) and letting the interactive visualizer compose its own labels.

3. Position normalisation

Because the interactive visualizer uses fixed-size axes (unlike the static one which adapts the figure to the data), I added a _normalize_positions() step that maps all positions to [0, 1]. Should this live in GraphVisualizer as a public static method, or is it fine to keep it internal to the interactive module?


4. Layout fallback for narrow graphs

When the flow-based layout produces a very tall/narrow result (aspect ratio < 0.3 — common with deep Pauli-flow graphs like QAOA), the interactive visualizer falls back to nx.spring_layout. Is there a preferred fallback layout or heuristic already used elsewhere in the project?

image image

@matulni
Copy link
Contributor

matulni commented Feb 18, 2026

Hi, thanks for the PR. It'll be easier to decide on your design questions by looking at specific code. So go ahead with 1. For 2, let's do b. For 3, it can leave in the interactive module for now. For 4, there's not any fallback procedure, but in general the flow should always be shown.

@Kitsunp
Copy link
Author

Kitsunp commented Feb 18, 2026

Hi @emlynsg, @matulni here's an update on what's changed based on your feedback.

Changes

Code sharing between modules

Extended GraphVisualizer so the interactive visualizer reuses its drawing logic instead of reimplementing it:

  • draw_nodes_role — now accepts node_facecolors, node_edgecolors, and node_size for per-node overrides.
  • draw_edges — now accepts edge_subset to draw only active edges.
  • get_label_fontsize(max_node) — new static method that extracts the adaptive font-size logic (was duplicated in two places).

InteractiveGraphVisualizer._draw_graph delegates to all three.

Layout & node overlap

  • Removed the spring_layout fallback — the flow-based layout is always used.
  • Removed the _normalize_positions() / _enforce_minimum_spacing() pipeline (~95 lines). Raw positions from get_layout() are now used directly, scaled by node_distance.
  • Figure height adapts to graph density so dense layouts (e.g. QAOA with 24 nodes in a column) get enough vertical space.
  • Axis limits auto-scale to the data range with a small margin.
image image

Node sizing

Node marker size and label font size are computed once at construction time from the actual figure/axes geometry:

points_per_unit = (ax_height_inches / y_range) * 72
marker_diameter = marker_fill_ratio * points_per_unit
node_size = marker_diameter²
label_fontsize = marker_diameter * label_size_ratio  (capped at max_label_fontsize)

This is deterministic — no heuristics that break on window resize or different graph sizes.

Configurable visual parameters

All visual constants are now public keyword-only constructor parameters:

Parameter Default Purpose
marker_fill_ratio 0.80 Fraction of inter-node spacing used by marker
label_size_ratio 0.55 Font size as fraction of marker diameter
max_label_fontsize 12 Upper bound for font size (prevents overflow in sparse graphs)
min_inches_per_node 0.3 Vertical inches per node for adaptive figure height
active_node_color #2060cc Border colour for active nodes
measured_node_color lightgray Fill colour for measured nodes

Visual style

  • Active nodes use a blue border (#2060cc) to differentiate from input nodes (which keep their red border from the static visualizer).

Design questions

1. Label drawing

The only remaining duplicated logic is label drawing — the interactive visualizer draws labels locally because it appends dynamic content (m=1, correction flags XZ).

Two options:

  • a) Add extra_labels: Mapping[int, str] | None to draw_node_labels so callers can inject extra text.
  • b) Leave it as is — labels are the only local drawing (current approach).

2. Layer ordering in the interactive visualizer

The interactive visualizer uses get_layout() from the static visualizer but doesn't reuse its layer-grouping or directional flow arrows. Should it try to replicate that visual structure, or is the current approach sufficient?

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.

3 participants

Comments