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 8efb5e6e7a9f..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", @@ -114,6 +115,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..8c77437e43a6 --- /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 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