Skip to content

Commit 1a5baaa

Browse files
committed
support differently directed subgraphs (fix #114)
Signed-off-by: Stefan Niederhauser <[email protected]>
1 parent 8af77ec commit 1a5baaa

File tree

2 files changed

+55
-33
lines changed

2 files changed

+55
-33
lines changed

graphviz-java/src/main/java/guru/nidi/graphviz/model/Serializer.java

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
*/
1616
package guru.nidi.graphviz.model;
1717

18-
import guru.nidi.graphviz.attribute.Label;
19-
import guru.nidi.graphviz.attribute.SimpleLabel;
18+
import guru.nidi.graphviz.attribute.*;
2019

2120
import java.util.*;
2221
import java.util.Map.Entry;
22+
import java.util.stream.Stream;
2323

2424
public class Serializer {
2525
private final MutableGraph graph;
@@ -31,12 +31,36 @@ public Serializer(MutableGraph graph) {
3131
}
3232

3333
public String serialize() {
34-
graph(graph, true);
34+
toplevelGraph(graph);
3535
return str.toString();
3636
}
3737

38-
private void graph(MutableGraph graph, boolean toplevel) {
39-
graphInit(graph, toplevel);
38+
private void toplevelGraph(MutableGraph graph) {
39+
final boolean useDir = hasDifferentlyDirectedSubgraphs(graph);
40+
str.append(graph.strict ? "strict " : "").append(graph.directed || useDir ? "digraph " : "graph ");
41+
if (!graph.name.isEmpty()) {
42+
str.append(SimpleLabel.of(graph.name).serialized()).append(' ');
43+
}
44+
str.append("{\n");
45+
doGraph(graph, useDir);
46+
str.append('}');
47+
}
48+
49+
private void subGraph(MutableGraph graph, boolean useDir) {
50+
if (!graph.name.isEmpty() || graph.cluster) {
51+
str.append("subgraph ")
52+
.append(Label.of((graph.cluster ? "cluster_" : "") + graph.name).serialized())
53+
.append(' ');
54+
}
55+
str.append("{\n");
56+
doGraph(graph, useDir);
57+
str.append('}');
58+
}
59+
60+
private void doGraph(MutableGraph graph, boolean useDir) {
61+
if (useDir && graph.graphAttrs.get("dir") == null) {
62+
attributes("edge", Attributes.attr("dir", graph.directed ? "forward" : "none"));
63+
}
4064
graphAttrs(graph);
4165

4266
final List<MutableNode> nodes = new ArrayList<>();
@@ -58,12 +82,17 @@ private void graph(MutableGraph graph, boolean toplevel) {
5882
}
5983

6084
nodes(graph, nodes);
61-
graphs(graphs, nodes);
85+
graphs(graphs, nodes, useDir);
6286

63-
edges(nodes);
64-
edges(graphs);
87+
edges(nodes, useDir);
88+
edges(graphs, useDir);
89+
}
6590

66-
str.append('}');
91+
private boolean hasDifferentlyDirectedSubgraphs(MutableGraph graph) {
92+
return Stream.concat(linkedNodes(graph.nodes).stream(), linkedNodes(graph.subgraphs).stream())
93+
.filter(n -> n instanceof MutableGraph)
94+
.map(n -> (MutableGraph) n)
95+
.anyMatch(sub -> sub.directed != graph.directed);
6796
}
6897

6998
private void graphAttrs(MutableGraph graph) {
@@ -72,20 +101,6 @@ private void graphAttrs(MutableGraph graph) {
72101
attributes("edge", graph.linkAttrs);
73102
}
74103

75-
private void graphInit(MutableGraph graph, boolean toplevel) {
76-
if (toplevel) {
77-
str.append(graph.strict ? "strict " : "").append(graph.directed ? "digraph " : "graph ");
78-
if (!graph.name.isEmpty()) {
79-
str.append(SimpleLabel.of(graph.name).serialized()).append(' ');
80-
}
81-
} else if (!graph.name.isEmpty() || graph.cluster) {
82-
str.append("subgraph ")
83-
.append(Label.of((graph.cluster ? "cluster_" : "") + graph.name).serialized())
84-
.append(' ');
85-
}
86-
str.append("{\n");
87-
}
88-
89104
private int indexOfName(List<MutableNode> nodes, Label name) {
90105
for (int i = 0; i < nodes.size(); i++) {
91106
if (nodes.get(i).name.equals(name)) {
@@ -95,7 +110,7 @@ private int indexOfName(List<MutableNode> nodes, Label name) {
95110
return -1;
96111
}
97112

98-
private void attributes(String name, MutableAttributed<?, ?> attributed) {
113+
private void attributes(String name, Attributes<?> attributed) {
99114
if (!attributed.isEmpty()) {
100115
str.append(name);
101116
attrs(attributed);
@@ -161,34 +176,34 @@ private boolean isNode(LinkTarget target, MutableNode node) {
161176
return target == node || (target instanceof ImmutablePortNode && ((ImmutablePortNode) target).node() == node);
162177
}
163178

164-
private void graphs(List<MutableGraph> graphs, List<MutableNode> nodes) {
179+
private void graphs(List<MutableGraph> graphs, List<MutableNode> nodes, boolean useDir) {
165180
for (final MutableGraph graph : graphs) {
166181
if (graph.links.isEmpty() && !isLinked(graph, nodes) && !isLinked(graph, graphs)) {
167-
graph(graph, false);
182+
subGraph(graph, useDir);
168183
str.append('\n');
169184
}
170185
}
171186
}
172187

173-
private void edges(List<? extends LinkSource> linkSources) {
188+
private void edges(List<? extends LinkSource> linkSources, boolean useDir) {
174189
for (final LinkSource linkSource : linkSources) {
175190
for (final Link link : linkSource.links()) {
176-
linkTarget(link.from);
177-
str.append(graph.directed ? " -> " : " -- ");
178-
linkTarget(link.to);
191+
linkTarget(link.from, useDir);
192+
str.append(graph.directed || useDir ? " -> " : " -- ");
193+
linkTarget(link.to, useDir);
179194
attrs(link.attributes);
180195
str.append('\n');
181196
}
182197
}
183198
}
184199

185-
private void linkTarget(Object linkable) {
200+
private void linkTarget(Object linkable, boolean useDir) {
186201
if (linkable instanceof MutableNode) {
187202
str.append(((MutableNode) linkable).name.serialized());
188203
} else if (linkable instanceof ImmutablePortNode) {
189204
port((ImmutablePortNode) linkable);
190205
} else if (linkable instanceof MutableGraph) {
191-
graph((MutableGraph) linkable, false);
206+
subGraph((MutableGraph) linkable, useDir);
192207
} else {
193208
throw new IllegalStateException("unexpected link target " + linkable);
194209
}
@@ -206,7 +221,7 @@ private void port(ImmutablePortNode portNode) {
206221
}
207222
}
208223

209-
private void attrs(MutableAttributed<?, ?> attrs) {
224+
private void attrs(Attributes<?> attrs) {
210225
if (!attrs.isEmpty()) {
211226
str.append(" [");
212227
boolean first = true;

graphviz-java/src/test/java/guru/nidi/graphviz/model/SerializerTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ void complexEdge() {
162162
.with(node("a").link(node("x"))));
163163
}
164164

165+
@Test
166+
void mixedDirected() {
167+
assertGraph("digraph {\nedge ['dir'='none']\n'a' -> {\nedge ['dir'='forward']\n'b' -> 'c'\n}\n}",
168+
graph().with(node("a").link(
169+
graph().directed().with(node("b").link(node("c"))))));
170+
}
171+
165172
private void assertGraph(String expected, Graph graph) {
166173
assertEquals(expected.replace("'", "\""), new Serializer((MutableGraph) graph).serialize());
167174
}

0 commit comments

Comments
 (0)