Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changes/newsfragments/7305.new
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds InterDependencies visualisation capability via ipywidgets and ipycytoscape
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ test = [
"zhinst.qcodes>=0.5", # typecheck zhinst driver alias
"libcst>=1.2.0", # refactor tests
"jinja2>=3.1.3", # transitive dependency pin due to cve in earlier version
"qcodes[live_plotting]",
]
docs = [
"autodocsumm>=0.2.9",
Expand All @@ -114,6 +115,9 @@ docs = [
refactor = [
"libcst>=1.2.0"
]
live_plotting = [
"ipycytoscape>=1.2.2"
]
Copy link
Collaborator

@jenshnielsen jenshnielsen Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should call this something like jupyer and then move the dependency on ipykernel and ipywidgets down here. The latter should probably not be done in this pr. That way we can give users the option to pull in the juypyter deps if they want to


[project.scripts]
qcodes-monitor = "qcodes.monitor.monitor:main"
Expand Down
9 changes: 0 additions & 9 deletions src/qcodes/dataset/descriptions/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,15 +322,6 @@ def __getitem__(self, name: str) -> ParamSpecBase:
def graph(self) -> nx.DiGraph[str]:
return self._graph

def to_ipycytoscape_json(self) -> dict[str, list[dict[str, Any]]]:
graph_json: dict[str, list[dict[str, Any]]] = nx.cytoscape_data(self.graph)[
"elements"
]
# TODO: Add different node types?
for edge_dict in graph_json["edges"]:
edge_dict["classes"] = edge_dict["data"]["interdep_type"]
return graph_json

@staticmethod
def validate_paramspectree(
paramspectree: ParamSpecTree, interdep_type: str | None = None
Expand Down
100 changes: 100 additions & 0 deletions src/qcodes/dataset/descriptions/dependencies_viz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
Utils for visualizing InterDependencies_. Note that these only work in
Jupyter and require you to have ipycytoscape installed. This can be
installed with ``pip install qcodes[live_plotting]``
"""

from typing import TYPE_CHECKING, Any

import ipycytoscape

Check failure on line 9 in src/qcodes/dataset/descriptions/dependencies_viz.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.13, false)

Stub file not found for "ipycytoscape" (reportMissingTypeStubs)

Check failure on line 9 in src/qcodes/dataset/descriptions/dependencies_viz.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.12, false)

Stub file not found for "ipycytoscape" (reportMissingTypeStubs)

Check failure on line 9 in src/qcodes/dataset/descriptions/dependencies_viz.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.12, false)

Stub file not found for "ipycytoscape" (reportMissingTypeStubs)
import ipywidgets

Check failure on line 10 in src/qcodes/dataset/descriptions/dependencies_viz.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.13, false)

Stub file not found for "ipywidgets" (reportMissingTypeStubs)

Check failure on line 10 in src/qcodes/dataset/descriptions/dependencies_viz.py

View workflow job for this annotation

GitHub Actions / pytestmypy (ubuntu-latest, 3.12, false)

Stub file not found for "ipywidgets" (reportMissingTypeStubs)

Check failure on line 10 in src/qcodes/dataset/descriptions/dependencies_viz.py

View workflow job for this annotation

GitHub Actions / pytestmypy (windows-latest, 3.12, false)

Stub file not found for "ipywidgets" (reportMissingTypeStubs)
import networkx as nx

if TYPE_CHECKING:
from qcodes.dataset.descriptions.dependencies import InterDependencies_

INTERDEPENDENCIES_STYLE = [
{
"selector": "node",
"css": {
"content": "data(id)",
"text-valign": "center",
"font-weight": 10,
"color": "white",
"text-outline-width": 1,
"text-outline-color": "#11479e",
"background-color": "#8aa8d8",
"border-color": "#11479e",
"border-width": 1.5,
"border-style": "solid",
},
},
{
"selector": "node[node_type='dependency']",
"css": {
"text-outline-color": "#089222",
"background-color": "#90C79A",
"border-color": "#089222",
},
},
{
"selector": "node[node_type='inference']",
"css": {
"text-outline-color": "#920808",
"background-color": "#D37B7B",
"border-color": "#920808",
},
},
{
"selector": "edge",
"style": {
"width": 4,
"curve-style": "bezier",
"target-arrow-shape": "triangle",
},
},
{
"selector": "edge[interdep_type = 'depends_on'], ",
"style": {
"line-color": "#089222",
"target-arrow-color": "#089222",
},
},
{
"selector": "edge[interdep_type = 'inferred_from']",
"style": {
"line-color": "#920808",
"target-arrow-color": "#920808",
},
},
]


def draw_interdepenencies(interdeps: "InterDependencies_") -> ipywidgets.HBox:
graphwidget = ipycytoscape.CytoscapeWidget()
graphwidget.graph.add_graph_from_json(interdeps_to_ipycytoscape_json(interdeps))
graphwidget.set_style(INTERDEPENDENCIES_STYLE)
graphwidget.user_zooming_enabled = True
graphwidget.min_zoom = 0.2
graphwidget.max_zoom = 5
graphwidget.set_layout(name="cola", animate=False, avoidOverlap=True)
graphwidget.wheel_sensitivity = 0.1
return ipywidgets.HBox([graphwidget])


def interdeps_to_ipycytoscape_json(
interdeps: "InterDependencies_",
) -> dict[str, list[dict[str, Any]]]:
graph_json: dict[str, list[dict[str, Any]]] = nx.cytoscape_data(interdeps.graph)[
"elements"
]
interdeps_dict = interdeps._to_dict()
dependencies = list(interdeps_dict["dependencies"].keys())
inferences = list(interdeps_dict["inferences"].keys())

for node_dict in graph_json["nodes"]:
if node_dict["data"]["id"] in dependencies:
node_dict["data"]["node_type"] = "dependency"
elif node_dict["data"]["id"] in inferences:
node_dict["data"]["node_type"] = "inference"
return graph_json
Loading