From b7c9ddf635467e46722d98c11c82188d1acdc4fd Mon Sep 17 00:00:00 2001 From: Samantha Ho Date: Thu, 10 Jul 2025 15:52:35 -0700 Subject: [PATCH 1/4] Add ipycytoscape plotting for InterDependencies_ objects --- pyproject.toml | 3 + .../dataset/descriptions/dependencies.py | 9 -- .../dataset/descriptions/dependencies_viz.py | 100 ++++++++++++++++++ 3 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 src/qcodes/dataset/descriptions/dependencies_viz.py diff --git a/pyproject.toml b/pyproject.toml index 8efb5e6e7a9f..773b2a9890a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,6 +114,9 @@ docs = [ refactor = [ "libcst>=1.2.0" ] +live_plotting = [ + "ipycytoscape>=1.2.2" +] [project.scripts] qcodes-monitor = "qcodes.monitor.monitor:main" diff --git a/src/qcodes/dataset/descriptions/dependencies.py b/src/qcodes/dataset/descriptions/dependencies.py index 54f9dd31be5a..a1e55241dd8f 100644 --- a/src/qcodes/dataset/descriptions/dependencies.py +++ b/src/qcodes/dataset/descriptions/dependencies.py @@ -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 diff --git a/src/qcodes/dataset/descriptions/dependencies_viz.py b/src/qcodes/dataset/descriptions/dependencies_viz.py new file mode 100644 index 000000000000..37d5e0a93c11 --- /dev/null +++ b/src/qcodes/dataset/descriptions/dependencies_viz.py @@ -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 +import ipywidgets +import netowrkx 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 From 2dbee12971dab04fe1c24400293b430a05c27c9c Mon Sep 17 00:00:00 2001 From: Samantha Ho Date: Wed, 16 Jul 2025 09:01:40 -0700 Subject: [PATCH 2/4] Add newsfragment Update test optional dependency to include live_plotting optional dependency --- docs/changes/newsfragments/7305.new | 1 + pyproject.toml | 1 + 2 files changed, 2 insertions(+) create mode 100644 docs/changes/newsfragments/7305.new diff --git a/docs/changes/newsfragments/7305.new b/docs/changes/newsfragments/7305.new new file mode 100644 index 000000000000..9e1af6f91604 --- /dev/null +++ b/docs/changes/newsfragments/7305.new @@ -0,0 +1 @@ +Adds InterDependencies visualisation capability via ipywidgets and ipycytoscape \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 773b2a9890a7..f0e247fadb78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", From b6a712d93a1c289537b77ef7ebd0dce9a4854dcd Mon Sep 17 00:00:00 2001 From: Samantha Ho Date: Wed, 16 Jul 2025 09:33:59 -0700 Subject: [PATCH 3/4] Fix typo in imports --- src/qcodes/dataset/descriptions/dependencies_viz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qcodes/dataset/descriptions/dependencies_viz.py b/src/qcodes/dataset/descriptions/dependencies_viz.py index 37d5e0a93c11..b0fd9070a373 100644 --- a/src/qcodes/dataset/descriptions/dependencies_viz.py +++ b/src/qcodes/dataset/descriptions/dependencies_viz.py @@ -8,7 +8,7 @@ import ipycytoscape import ipywidgets -import netowrkx as nx +import networkx as nx if TYPE_CHECKING: from qcodes.dataset.descriptions.dependencies import InterDependencies_ From 2b4f2375a53dcd051b91e373e1cc7a7af49acda1 Mon Sep 17 00:00:00 2001 From: Samantha Ho Date: Mon, 21 Jul 2025 11:33:12 -0700 Subject: [PATCH 4/4] Fix typo in Interdependencies style --- src/qcodes/dataset/descriptions/dependencies_viz.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qcodes/dataset/descriptions/dependencies_viz.py b/src/qcodes/dataset/descriptions/dependencies_viz.py index b0fd9070a373..8c77437e43a6 100644 --- a/src/qcodes/dataset/descriptions/dependencies_viz.py +++ b/src/qcodes/dataset/descriptions/dependencies_viz.py @@ -30,7 +30,7 @@ }, }, { - "selector": "node[node_type=`dependency`]", + "selector": "node[node_type='dependency']", "css": { "text-outline-color": "#089222", "background-color": "#90C79A", @@ -38,7 +38,7 @@ }, }, { - "selector": "node[node_type=`inference`]", + "selector": "node[node_type='inference']", "css": { "text-outline-color": "#920808", "background-color": "#D37B7B",