Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 7 additions & 2 deletions hugr-py/src/hugr/hugr/render.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Visualise HUGR using graphviz."""

import html
from collections.abc import Iterable
from dataclasses import dataclass, field

Expand Down Expand Up @@ -101,7 +102,9 @@ def render(self, hugr: Hugr) -> Digraph:
"margin": "0",
"bgcolor": self.config.palette.background,
}
if not (name := hugr[hugr.module_root].metadata.get("name", None)):
if name := hugr[hugr.module_root].metadata.get("name", None):
name = html.escape(name)
else:
name = ""

graph = gv.Digraph(name, strict=False)
Expand Down Expand Up @@ -215,7 +218,8 @@ def _viz_node(self, node: Node, hugr: Hugr, graph: Digraph) -> None:
meta = hugr[node].metadata
if len(meta) > 0:
data = "<BR/><BR/>" + "<BR/>".join(
f"{key}: {value}" for key, value in meta.items()
html.escape(key) + ": " + html.escape(value)
for key, value in meta.items()
)
else:
data = ""
Expand All @@ -236,6 +240,7 @@ def _viz_node(self, node: Node, hugr: Hugr, graph: Digraph) -> None:
op_name = op.op_def().name
else:
op_name = op.name()
op_name = html.escape(op_name)

label_config = {
"node_back_color": self.config.palette.node,
Expand Down
123 changes: 122 additions & 1 deletion hugr-py/tests/__snapshots__/test_hugr_build.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@
<TD>
<TABLE BORDER="0" CELLBORDER="0">
<TR><TD><FONT POINT-SIZE="11.0" FACE="monospace"
COLOR="black"><B>Const(Function(body=Hugr(module_root=Node(0), entrypoint=Node(4), _nodes=[NodeData(op=Module(), parent=None, metadata={}), NodeData(op=FuncDefn(f_name='main', inputs=[Qubit], params=[]), parent=Node(0), metadata={}), NodeData(op=Input(types=[Qubit]), parent=Node(1), metadata={}), NodeData(op=Output(), parent=Node(1), metadata={}), NodeData(op=DFG(inputs=[Qubit]), parent=Node(1), metadata={}), NodeData(op=Input(types=[Qubit]), parent=Node(4), metadata={}), NodeData(op=Output(), parent=Node(4), metadata={}), NodeData(op=Noop(Qubit), parent=Node(4), metadata={})], _links=BiMap({_SubPort(port=OutPort(Node(2), 0), sub_offset=0): _SubPort(port=InPort(Node(4), 0), sub_offset=0), _SubPort(port=OutPort(Node(5), 0), sub_offset=0): _SubPort(port=InPort(Node(7), 0), sub_offset=0), _SubPort(port=OutPort(Node(7), 0), sub_offset=0): _SubPort(port=InPort(Node(6), 0), sub_offset=0), _SubPort(port=OutPort(Node(4), 0), sub_offset=0): _SubPort(port=InPort(Node(3), 0), sub_offset=0)}), _free_nodes=[])))</B></FONT></TD></TR>
COLOR="black"><B>Const(Function(body=Hugr(module_root=Node(0), entrypoint=Node(4), _nodes=[NodeData(op=Module(), parent=None, metadata={}), NodeData(op=FuncDefn(f_name=&#x27;main&#x27;, inputs=[Qubit], params=[]), parent=Node(0), metadata={}), NodeData(op=Input(types=[Qubit]), parent=Node(1), metadata={}), NodeData(op=Output(), parent=Node(1), metadata={}), NodeData(op=DFG(inputs=[Qubit]), parent=Node(1), metadata={}), NodeData(op=Input(types=[Qubit]), parent=Node(4), metadata={}), NodeData(op=Output(), parent=Node(4), metadata={}), NodeData(op=Noop(Qubit), parent=Node(4), metadata={})], _links=BiMap({_SubPort(port=OutPort(Node(2), 0), sub_offset=0): _SubPort(port=InPort(Node(4), 0), sub_offset=0), _SubPort(port=OutPort(Node(5), 0), sub_offset=0): _SubPort(port=InPort(Node(7), 0), sub_offset=0), _SubPort(port=OutPort(Node(7), 0), sub_offset=0): _SubPort(port=InPort(Node(6), 0), sub_offset=0), _SubPort(port=OutPort(Node(4), 0), sub_offset=0): _SubPort(port=InPort(Node(3), 0), sub_offset=0)}), _free_nodes=[])))</B></FONT></TD></TR>
</TABLE>
</TD>
</TR>
Expand Down Expand Up @@ -1109,6 +1109,127 @@

'''
# ---
# name: test_html_labels
'''
digraph "&lt;i&gt;Module Root&lt;/i&gt;" {
bgcolor=white margin=0 nodesep=0.15 rankdir="" ranksep=0.1
subgraph cluster0 {
subgraph cluster1 {
2 [label=<
<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="1" CELLPADDING="1"
BGCOLOR="#ACCBF9" COLOR="white">

<TR>
<TD>
<TABLE BORDER="0" CELLBORDER="0">
<TR><TD><FONT POINT-SIZE="11.0" FACE="monospace"
COLOR="black"><B>Input</B></FONT></TD></TR>
</TABLE>
</TD>
</TR>

<TR>
<TD>
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="3" CELLPADDING="2">
<TR>
<TD BGCOLOR="white" COLOR="#1CADE4" PORT="out.0" BORDER="1"><FONT POINT-SIZE="10.0" FACE="monospace" COLOR="black">0</FONT></TD>
</TR>
</TABLE>
</TD>
</TR>

</TABLE>
> shape=plain]
3 [label=<
<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="1" CELLPADDING="1"
BGCOLOR="#ACCBF9" COLOR="white">

<TR>
<TD>
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="3" CELLPADDING="2">
<TR>
<TD BGCOLOR="white" COLOR="#1CADE4" PORT="in.0" BORDER="1"><FONT POINT-SIZE="10.0" FACE="monospace" COLOR="black">0</FONT></TD>
</TR>
</TABLE>
</TD>
</TR>

<TR>
<TD>
<TABLE BORDER="0" CELLBORDER="0">
<TR><TD><FONT POINT-SIZE="11.0" FACE="monospace"
COLOR="black"><B>Output</B></FONT></TD></TR>
</TABLE>
</TD>
</TR>

</TABLE>
> shape=plain]
4 [label=<
<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="1" CELLPADDING="1"
BGCOLOR="#ACCBF9" COLOR="white">

<TR>
<TD>
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="3" CELLPADDING="2">
<TR>
<TD BGCOLOR="white" COLOR="#1CADE4" PORT="in.0" BORDER="1"><FONT POINT-SIZE="10.0" FACE="monospace" COLOR="black">0</FONT></TD>
</TR>
</TABLE>
</TD>
</TR>

<TR>
<TD>
<TABLE BORDER="0" CELLBORDER="0">
<TR><TD><FONT POINT-SIZE="11.0" FACE="monospace"
COLOR="black"><B>Some</B></FONT></TD></TR>
</TABLE>
</TD>
</TR>

</TABLE>
> shape=plain]
1 [label=<
<TABLE BORDER="2" CELLBORDER="0" CELLSPACING="1" CELLPADDING="1"
BGCOLOR="#1CADE4" COLOR="#F4A261">

<TR>
<TD>
<TABLE BORDER="0" CELLBORDER="0">
<TR><TD><FONT POINT-SIZE="11.0" FACE="monospace"
COLOR="black"><B><b>[FuncDefn(&lt;jupyter-notebook&gt;)]</b></B><BR/><BR/>label: &lt;b&gt;Bold Label&lt;/b&gt;<BR/>&lt;other-label&gt;: &lt;i&gt;Italic Label&lt;/i&gt;</FONT></TD></TR>
</TABLE>
</TD>
</TR>

</TABLE>
> shape=plain]
color="#F4A261" label="" margin=10 penwidth=2
}
0 [label=<
<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="1" CELLPADDING="1"
BGCOLOR="#1CADE4" COLOR="#1CADE4">

<TR>
<TD>
<TABLE BORDER="0" CELLBORDER="0">
<TR><TD><FONT POINT-SIZE="11.0" FACE="monospace"
COLOR="black"><B>Module</B><BR/><BR/>name: &lt;i&gt;Module Root&lt;/i&gt;</FONT></TD></TR>
</TABLE>
</TD>
</TR>

</TABLE>
> shape=plain]
color="#1CADE4" label="" margin=10 penwidth=1
}
2:"out.0" -> 4:"in.0" [label=Bool arrowhead=none arrowsize=1.0 color="#1CADE4" fontcolor=black fontname=monospace fontsize=9 penwidth=1.5]
2:"out.0" -> 3:"in.0" [label=Bool arrowhead=none arrowsize=1.0 color="#1CADE4" fontcolor=black fontname=monospace fontsize=9 penwidth=1.5]
}

'''
# ---
# name: test_insert_nested
'''
digraph {
Expand Down
22 changes: 21 additions & 1 deletion hugr-py/tests/test_hugr_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import hugr.ops as ops
import hugr.tys as tys
import hugr.val as val
from hugr.build.dfg import Dfg, _ancestral_sibling
from hugr.build.dfg import Dfg, Function, _ancestral_sibling
from hugr.build.function import Module
from hugr.hugr import Hugr
from hugr.hugr.node_port import Node, _SubPort
Expand Down Expand Up @@ -404,3 +404,23 @@ def test_option() -> None:
dfg.set_outputs(b)

validate(dfg.hugr)


def test_html_labels(snapshot) -> None:
"""Ensures that HTML-like labels can be processed correctly by both the builder and
the renderer.
"""
f = Function(
"<jupyter-notebook>",
[tys.Bool],
)
f.metadata["label"] = "<b>Bold Label</b>"
f.metadata["<other-label>"] = "<i>Italic Label</i>"

f.hugr[f.hugr.module_root].metadata["name"] = "<i>Module Root</i>"

b = f.inputs()[0]
f.add_op(ops.Some(tys.Bool), b)
f.set_outputs(b)

validate(f.hugr, snap=snapshot)
Loading