diff --git a/auxiliary/libs/signalflow_visualisation/__init__.py b/auxiliary/libs/signalflow_visualisation/__init__.py new file mode 100644 index 00000000..0faddf8c --- /dev/null +++ b/auxiliary/libs/signalflow_visualisation/__init__.py @@ -0,0 +1,80 @@ +import json +import networkx as nx +from IPython.display import SVG +from signalflow import Patch + +def visualise_patch_structure(patch: Patch): + """ + Renders the structure of a patch as a directed graph. + + Requires: + - networkx + - pygraphviz (https://github.com/pygraphviz/pygraphviz/issues/11) + + Args: + patch (Patch): The patch to diagram. + + Returns: + An IPython SVG object that can be rendered in a notebook. + + TODO: Implement support for cyclical graphs (requires reformulating JSON using JSON pointers) + """ + + G = nx.DiGraph() + + def label_map(label): + lookup = { + "add": "+", + "multiply": "×", + "subtract": "-", + "divide": "÷" + } + if label in lookup.keys(): + return lookup[label] + else: + return label + + spec = patch.to_spec() + structure = json.loads(spec.to_json()) + nodes = structure["nodes"] + for node in nodes: + node_label = node["node"] + node_label = label_map(node_label) + node_label = "%s" % node_label + + node_label += "
" + for input_key, input_value in node["inputs"].items(): + if not isinstance(input_value, dict): + node_label += "

%s = %s" % (input_key, round(input_value, 7)) + node_label += "
" + + # special graphviz syntax for enabling HTML formatting in node labels + node_label = "<%s>" % node_label + G.add_node(node["id"], label=node_label) + for node in nodes: + for input_key, input_value in node["inputs"].items(): + if isinstance(input_value, dict): + label = "" + if not input_key.startswith("input"): + label = input_key + # white background + # label = "<
%s
>" % label + G.add_edge(input_value["id"], node["id"], label=label) + + ag = nx.nx_agraph.to_agraph(G) + ag.graph_attr["splines"] = "polyline" + ag.node_attr["penwidth"] = 0.5 + ag.node_attr["fontname"] = "helvetica" + ag.node_attr["fontsize"] = 9 + ag.node_attr["margin"] = 0.12 + ag.node_attr["height"] = 0.3 + ag.edge_attr["fontname"] = "helvetica" + ag.edge_attr["fontsize"] = 8 + ag.edge_attr["penwidth"] = 0.5 + ag.edge_attr["arrowsize"] = 0.5 + ag.edge_attr["labelfloat"] = False + ag.edge_attr["labeldistance"] = 0 + ag.node_attr["shape"] = "rectangle" + ag.layout(prog='dot') + svg = ag.draw(format='svg') + return SVG(svg) \ No newline at end of file diff --git a/setup.py b/setup.py index 2895f4ec..2d93693e 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,12 @@ def build_extension(self, ext): shutil.copy(os.path.join(self.build_temp, cfg, libname), "auxiliary/libs/signalflow") -signalflow_packages = ['signalflow_midi', 'signalflow-stubs', 'signalflow_examples'] +signalflow_packages = [ + 'signalflow-stubs', + 'signalflow_midi', + 'signalflow_examples', + 'signalflow_visualisation' +] signalflow_package_data = [] if sys.platform == 'win32': # --------------------------------------------------------------------------------