mirror of
https://github.com/FliegendeWurst/hypergraph-drawing.git
synced 2024-12-04 14:19:09 +00:00
Initial commit
This commit is contained in:
commit
72dec5ebc9
43
README.md
Normal file
43
README.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Hypergraph drawing using Graphviz and Asymptote
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Requires Python 3, [pydot](https://pypi.org/project/pydot/) (tested with 1.4.2), [Asymptote](https://asymptote.sourceforge.io/) (tested with 2.67) and LaTeX (tested with TeX Live 2021).
|
||||||
|
|
||||||
|
To render a hypergraph specified in `graph.dot`, execute the following:
|
||||||
|
```bash
|
||||||
|
cat graph.dot | dot -Tdot | python3 ./dot_hypergraph_to_asymptote.py /dev/stdin > graph.asy
|
||||||
|
asy -f png graph.asy
|
||||||
|
```
|
||||||
|
You may adjust the font size, width/height of the output and more by editing the values at the beginning of the script.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
| Original graph | Asymptote rendering |
|
||||||
|
| -------------- | ------------------- |
|
||||||
|
| ![dot rendering](./graph_dot.png) | ![asymptote rendering](./graph.png) |
|
||||||
|
|
||||||
|
`graph.dot`:
|
||||||
|
```
|
||||||
|
digraph {
|
||||||
|
edge [dir="back"];
|
||||||
|
"Node 1" -> "Node 2" [label="Edge 1"]
|
||||||
|
"Node 1" -> "Node 3" [label="Edge 1"]
|
||||||
|
"Node 3" -> "Node 4" [label="Edge 2"]
|
||||||
|
"Node 3" -> "Node 5" [label="Edge 2"]
|
||||||
|
"Node 5" -> "Node 6" [label="Edge 3"]
|
||||||
|
"Node 4" -> "Node 6" [label="Edge 3"]
|
||||||
|
"Node 2" -> "Node 7" [label="Edge 4"]
|
||||||
|
"Node 6" -> "Node 7" [label="Edge 4"]
|
||||||
|
"Node 2" -> "Node 8" [label="Edge 5"]
|
||||||
|
"Node 7" -> "Node 8" [label="Edge 5"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
The generated code is based on https://tex.stackexchange.com/a/108099/185782 by g.kov.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
|
167
dot_hypergraph_to_asymptote.py
Executable file
167
dot_hypergraph_to_asymptote.py
Executable file
@ -0,0 +1,167 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import itertools
|
||||||
|
import pydot # tested with 1.4.2
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("Usage: dot_hypergraph_to_asymptote.py <dot file>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# output size
|
||||||
|
width = None # 400
|
||||||
|
height = 639
|
||||||
|
|
||||||
|
# size of node and edge labels
|
||||||
|
fontsize = "20pt"
|
||||||
|
|
||||||
|
# specify minimum text width
|
||||||
|
# (useful if your labels contain wide unicode characters)
|
||||||
|
text_width_base = 35 # 40
|
||||||
|
text_width_factor = 5.5 # 9
|
||||||
|
|
||||||
|
# draw edges reversed
|
||||||
|
reverse_edges = True
|
||||||
|
|
||||||
|
# output \begin{asy} ... \end{asy}
|
||||||
|
output_latex = False
|
||||||
|
|
||||||
|
graphs = pydot.graph_from_dot_file(sys.argv[1])
|
||||||
|
graph = graphs[0]
|
||||||
|
|
||||||
|
nodes = dict()
|
||||||
|
node_list = []
|
||||||
|
|
||||||
|
for node in graph.get_nodes():
|
||||||
|
pos = node.get_pos()
|
||||||
|
if pos is None:
|
||||||
|
continue
|
||||||
|
pos = pos[1:-1]
|
||||||
|
nodes[node.get_name()] = tuple(map(float, pos.split(",")))
|
||||||
|
node_list.append(node.get_name())
|
||||||
|
|
||||||
|
edges = dict()
|
||||||
|
|
||||||
|
for edge in graph.get_edges():
|
||||||
|
label = edge.get_label()
|
||||||
|
if label is None:
|
||||||
|
continue
|
||||||
|
label = label.replace('"', "")
|
||||||
|
src = edge.get_source()
|
||||||
|
dest = edge.get_destination()
|
||||||
|
if label not in edges:
|
||||||
|
edges[label] = [(src, dest)]
|
||||||
|
else:
|
||||||
|
edges[label].append((src, dest))
|
||||||
|
|
||||||
|
hyperedges = []
|
||||||
|
|
||||||
|
for label in edges:
|
||||||
|
xmin = min(itertools.chain(*map(lambda x: [nodes[x[0]][0], nodes[x[1]][0]], edges[label])))
|
||||||
|
xmax = max(itertools.chain(*map(lambda x: [nodes[x[0]][0], nodes[x[1]][0]], edges[label])))
|
||||||
|
ymin = min(map(lambda x: nodes[x[0]][1], edges[label]))
|
||||||
|
ymax = max(map(lambda x: nodes[x[1]][1], edges[label]))
|
||||||
|
x = (xmax + xmin) / 2
|
||||||
|
y = (ymax + ymin) / 2
|
||||||
|
#print(label, xmin, xmax, ymin, ymax, x, y)
|
||||||
|
hyperedges.append(((x, y), label))
|
||||||
|
#input("")
|
||||||
|
|
||||||
|
if output_latex:
|
||||||
|
print("\\begin{asy}")
|
||||||
|
|
||||||
|
# code based on https://tex.stackexchange.com/a/108099/185782 (g.kov)
|
||||||
|
|
||||||
|
if width is None:
|
||||||
|
width = 0
|
||||||
|
if height is None:
|
||||||
|
height = 0
|
||||||
|
print("size(", width, ",", height, ");")
|
||||||
|
print("import flowchart;")
|
||||||
|
if fontsize is not None:
|
||||||
|
print("defaultpen(fontsize(", fontsize, "));")
|
||||||
|
|
||||||
|
print("pair[]pv={")
|
||||||
|
|
||||||
|
for node in node_list:
|
||||||
|
print(nodes[node], ",")
|
||||||
|
|
||||||
|
print("};")
|
||||||
|
|
||||||
|
print("string[]pv_labels={")
|
||||||
|
|
||||||
|
for name in node_list:
|
||||||
|
if not name.startswith('"'):
|
||||||
|
name = '"' + name
|
||||||
|
if not name[-1] == '"':
|
||||||
|
name = name + '"'
|
||||||
|
print(name, ',')
|
||||||
|
|
||||||
|
print("};")
|
||||||
|
|
||||||
|
print("real[]pv_size={")
|
||||||
|
|
||||||
|
for node in node_list:
|
||||||
|
print(text_width_base + len(node[1:-1])*text_width_factor, ',')
|
||||||
|
|
||||||
|
print("};")
|
||||||
|
|
||||||
|
print("pair[][] pe={")
|
||||||
|
|
||||||
|
for x in hyperedges:
|
||||||
|
print("{", x[0], ",E},")
|
||||||
|
|
||||||
|
print("};")
|
||||||
|
|
||||||
|
print("string[] pe_labels={")
|
||||||
|
|
||||||
|
for x in hyperedges:
|
||||||
|
print('"', x[1].replace("_", "\\_"), '",')
|
||||||
|
|
||||||
|
print("};")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
pen nodeFill=white;
|
||||||
|
pen nodeLine=darkblue+1.2pt+opacity(0.5);
|
||||||
|
|
||||||
|
block[] v=new block[pv.length];
|
||||||
|
block[] e=new block[pe.length];
|
||||||
|
|
||||||
|
|
||||||
|
for(int i=0;i<pv.length;++i){
|
||||||
|
v[i]=rectangle(pv_labels[i],pv[i],nodeFill,nodeLine,minwidth=pv_size[i]);
|
||||||
|
draw(v[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
string s;
|
||||||
|
for(int i=0;i<pe.length;++i){
|
||||||
|
s=pe_labels[i];
|
||||||
|
e[i]=circle(s,pe[i][0]);
|
||||||
|
label(s,pe[i][0],pe[i][1]);
|
||||||
|
dot(pe[i][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(
|
||||||
|
new void(picture pic, transform t) {
|
||||||
|
blockconnector operator --=blockconnector(pic,t);
|
||||||
|
real tg;
|
||||||
|
pen linePen=darkblue+1.2pt;
|
||||||
|
currentpen=linePen;
|
||||||
|
arrowfactor=4;
|
||||||
|
""")
|
||||||
|
|
||||||
|
for n, edge in enumerate(hyperedges):
|
||||||
|
label = edge[1]
|
||||||
|
for e in edges[label]:
|
||||||
|
i1 = node_list.index(e[0])
|
||||||
|
i2 = node_list.index(e[1])
|
||||||
|
if reverse_edges:
|
||||||
|
print("tg=90;")
|
||||||
|
print("draw(pic,v[%d].top(t){dir(90)} .. {dir(tg)}t*e[%d].center{dir(tg)} .. v[%d].bottom(t){dir(90)}, Arrow(HookHead));" % (i2, n, i1))
|
||||||
|
else:
|
||||||
|
print("tg=270;")
|
||||||
|
print("draw(pic,v[%d].bottom(t){dir(270)} .. {dir(tg)}t*e[%d].center{dir(tg)} .. v[%d].top(t){dir(270)},Arrow(HookHead));" % (i1, n, i2))
|
||||||
|
|
||||||
|
print("});")
|
||||||
|
if output_latex:
|
||||||
|
print("\\end{asy}")
|
13
graph.dot
Normal file
13
graph.dot
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
digraph {
|
||||||
|
edge [dir="back"];
|
||||||
|
"Node 1" -> "Node 2" [label="Edge 1"]
|
||||||
|
"Node 1" -> "Node 3" [label="Edge 1"]
|
||||||
|
"Node 3" -> "Node 4" [label="Edge 2"]
|
||||||
|
"Node 3" -> "Node 5" [label="Edge 2"]
|
||||||
|
"Node 5" -> "Node 6" [label="Edge 3"]
|
||||||
|
"Node 4" -> "Node 6" [label="Edge 3"]
|
||||||
|
"Node 2" -> "Node 7" [label="Edge 4"]
|
||||||
|
"Node 6" -> "Node 7" [label="Edge 4"]
|
||||||
|
"Node 2" -> "Node 8" [label="Edge 5"]
|
||||||
|
"Node 7" -> "Node 8" [label="Edge 5"]
|
||||||
|
}
|
BIN
graph_dot.png
Normal file
BIN
graph_dot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 53 KiB |
Loading…
Reference in New Issue
Block a user