Skip to content

Commit f21e2d3

Browse files
committed
Lots of vis work; import/export operations.
1 parent a1f62c8 commit f21e2d3

File tree

10 files changed

+223
-42
lines changed

10 files changed

+223
-42
lines changed

tensortapestry-loom/src/main/java/org/tensortapestry/graphviz/FormatUtils.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.awt.*;
44
import java.util.stream.Collectors;
55
import javax.annotation.Nonnull;
6+
67
import lombok.experimental.UtilityClass;
78

89
@UtilityClass
@@ -13,11 +14,20 @@ public String escape(@Nonnull String s) {
1314
return s.replace("\\", "\\\\").replace("\"", "\\\"");
1415
}
1516

17+
@Nonnull
18+
public String colorToRgbaString(Color c) {
19+
var s = "#%02x%02x%02x".formatted(c.getRed(), c.getGreen(), c.getBlue());
20+
if (c.getAlpha() != 255) {
21+
s += ":%02x".formatted(c.getAlpha());
22+
}
23+
return s;
24+
}
25+
1626
@Nonnull
1727
public String formatValue(@Nonnull Object value) {
1828
return switch (value) {
1929
case String s -> "\"%s\"".formatted(escape(s));
20-
case Color c -> "\"#%02x%02x%02x\"".formatted(c.getRed(), c.getGreen(), c.getBlue());
30+
case Color c -> "\"%s\"".formatted(colorToRgbaString(c));
2131
default -> value.toString();
2232
};
2333
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.tensortapestry.loom.graph.dialects.tensorops;
2+
3+
import lombok.Builder;
4+
import lombok.Value;
5+
import lombok.extern.jackson.Jacksonized;
6+
7+
@Value
8+
@Jacksonized
9+
@Builder
10+
public class IOSequencePoint {
11+
}

tensortapestry-loom/src/main/java/org/tensortapestry/loom/graph/dialects/tensorops/TensorOpNodes.java

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ public class TensorOpNodes {
88
// TODO: Switch to lookup by anchor when this is fixed:
99
// See: https://github.com/networknt/json-schema-validator/pull/930
1010

11+
public final String IO_SEQUENCE_POINT_TYPE =
12+
"http://tensortapestry.org/schemas/loom/2024-01/annotation_types.jsd#/annotations/IOSequencePoint";
13+
1114
public final String IPF_SIGNATURE_ANNOTATION_TYPE =
1215
"http://tensortapestry.org/schemas/loom/2024-01/annotation_types.jsd#/annotations/IPFSignature";
1316
public final String IPF_INDEX_ANNOTATION_TYPE =

tensortapestry-loom/src/main/java/org/tensortapestry/loom/graph/export/graphviz/ApplicationNodeExporter.java

+49-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package org.tensortapestry.loom.graph.export.graphviz;
22

3+
import java.awt.*;
34
import java.util.*;
5+
import java.util.List;
6+
47
import org.tensortapestry.graphviz.*;
58
import org.tensortapestry.loom.graph.LoomNode;
69
import org.tensortapestry.loom.graph.dialects.tensorops.*;
@@ -13,19 +16,27 @@ public void exportNode(
1316
GraphVisualizer.ExportContext context,
1417
LoomNode appNode
1518
) {
19+
DotGraph dotGraph = context.getDotGraph();
1620
var application = ApplicationNode.wrap(appNode);
1721
var operation = application.getOperationNode();
1822

19-
var operationColor = context.colorSchemeForNode(operation.getId()).getKey();
23+
var opCluster = dotGraph.assertLookup(operation.getId() + "_op_cluster", DotGraph.Cluster.class);
2024

21-
DotGraph dotGraph = context.getDotGraph();
22-
var dotCluster = dotGraph.createCluster("app_%s".formatted(appNode.getId()));
25+
Color operationColor = context.colorSchemeForNode(operation.getId()).getKey();
26+
27+
var clusterColor = "%s:%s".formatted(
28+
FormatUtils.colorToRgbaString(operationColor.brighter()),
29+
FormatUtils.colorToRgbaString(operationColor));
30+
31+
var dotCluster = opCluster.createCluster("app_%s".formatted(appNode.getId()));
2332
dotCluster
2433
.getAttributes()
2534
.set(GraphvizAttribute.NODESEP, 0.2)
2635
.set(GraphvizAttribute.RANKSEP, 0.2)
27-
.set(GraphvizAttribute.FILLCOLOR, operationColor.brighter())
28-
.set(GraphvizAttribute.STYLE, "filled, dashed, rounded");
36+
.set(GraphvizAttribute.PENWIDTH, 2)
37+
.set(GraphvizAttribute.BGCOLOR, clusterColor)
38+
.set(GraphvizAttribute.GRADIENTANGLE, 315)
39+
.set(GraphvizAttribute.STYLE, "filled, dashed, bold, rounded");
2940

3041
var dotOpNode = dotGraph.assertLookup(operation.getId().toString(), DotGraph.Node.class);
3142

@@ -55,8 +66,10 @@ public void exportNode(
5566

5667
dotNode.set(GraphvizAttribute.LABEL, HtmlLabel.from(labelTable));
5768

58-
selectionMapNodes(context, dotCluster, application, dotNode, application.getInputs(), true);
59-
selectionMapNodes(context, dotCluster, application, dotNode, application.getOutputs(), false);
69+
var useRouteNodes = operation.getApplicationNodes().stream().count() > 1;
70+
71+
selectionMapNodes(context, dotCluster, application, dotNode, application.getInputs(), useRouteNodes, true);
72+
selectionMapNodes(context, dotCluster, application, dotNode, application.getOutputs(), useRouteNodes, false);
6073
}
6174

6275
protected static void selectionMapNodes(
@@ -65,6 +78,7 @@ protected static void selectionMapNodes(
6578
ApplicationNode application,
6679
DotGraph.Node dotNode,
6780
Map<String, List<TensorSelection>> inputs,
81+
boolean useRouteNodes,
6882
boolean isInput
6983
) {
7084
var dotGraph = context.getDotGraph();
@@ -76,6 +90,7 @@ protected static void selectionMapNodes(
7690

7791
selCluster
7892
.getAttributes()
93+
.set(GraphvizAttribute.STYLE, "invis")
7994
.set(GraphvizAttribute.PERIPHERIES, 0)
8095
.set(GraphvizAttribute.RANK, "same");
8196

@@ -120,22 +135,44 @@ protected static void selectionMapNodes(
120135
)
121136
);
122137

123-
var routeId = "%s_route_%s_%s".formatted(application.getOperationId(), ioDesc, tensorId);
124-
var routeNode = context.getDotGraph().assertLookup(routeId, DotGraph.Node.class);
138+
DotGraph.Node sourceNode;
139+
if (useRouteNodes) {
140+
var routeId = "%s_route_%s_%s".formatted(application.getOperationId(), ioDesc, tensorId);
141+
sourceNode = context.getDotGraph().assertLookup(routeId, DotGraph.Node.class);
142+
} else {
143+
sourceNode = context.getDotGraph().assertLookup(tensorId.toString(), DotGraph.Node.class);
144+
}
125145

126146
DotGraph.Edge routeEdge;
127147
DotGraph.Edge selEdge;
128148

129149
if (isInput) {
130-
routeEdge = dotGraph.createEdge(routeNode, dotSelectionNode);
150+
routeEdge = dotGraph.createEdge(sourceNode, dotSelectionNode);
131151
selEdge = dotCluster.createEdge(dotSelectionNode, dotNode);
152+
153+
if (useRouteNodes) {
154+
routeEdge.set(GraphvizAttribute.TAILCLIP, false);
155+
}
156+
132157
} else {
133158
selEdge = dotCluster.createEdge(dotNode, dotSelectionNode);
134-
routeEdge = dotGraph.createEdge(dotSelectionNode, routeNode);
159+
routeEdge = dotGraph.createEdge(dotSelectionNode, sourceNode);
160+
161+
if (useRouteNodes) {
162+
routeEdge.set(GraphvizAttribute.HEADCLIP, false);
163+
}
135164
}
136165

166+
selEdge
167+
.set(GraphvizAttribute.COLOR, tensorColor)
168+
.set(GraphvizAttribute.ARROWHEAD, "none");
169+
routeEdge
170+
.set(GraphvizAttribute.COLOR, tensorColor + "C0")
171+
.set(GraphvizAttribute.ARROWHEAD, "none");
172+
137173
for (var e : List.of(routeEdge, selEdge)) {
138-
e.set(GraphvizAttribute.COLOR, tensorColor).set(GraphvizAttribute.PENWIDTH, 12);
174+
e
175+
.set(GraphvizAttribute.PENWIDTH, 24);
139176
}
140177

141178
// Force the selection nodes to cluster and layout in call order.

tensortapestry-loom/src/main/java/org/tensortapestry/loom/graph/export/graphviz/GraphVisualizer.java

+15-15
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
import guru.nidi.graphviz.attribute.Rank;
88
import guru.nidi.graphviz.engine.Graphviz;
99
import guru.nidi.graphviz.engine.GraphvizCmdLineEngine;
10+
1011
import java.awt.Color;
1112
import java.util.*;
1213
import java.util.List;
1314
import javax.annotation.Nonnull;
1415
import javax.annotation.Nullable;
16+
1517
import lombok.Builder;
1618
import lombok.Data;
1719
import lombok.Getter;
@@ -140,11 +142,13 @@ private Map<UUID, String> renderNodeHexAliasMap() {
140142
);
141143
}
142144

143-
@Nullable private LoomNode maybeNode(UUID id) {
145+
@Nullable
146+
private LoomNode maybeNode(UUID id) {
144147
return graph.getNode(id);
145148
}
146149

147-
@Nullable private LoomNode maybeNode(String idString) {
150+
@Nullable
151+
private LoomNode maybeNode(String idString) {
148152
try {
149153
var id = UUID.fromString(idString);
150154
return maybeNode(id);
@@ -163,7 +167,7 @@ private void export() {
163167
.set(GraphvizAttribute.CONCENTRATE, true)
164168
// .set(GraphAttribute.CLUSTERRANK, "local")
165169
// .set(GraphAttribute.NODESEP, 0.4)
166-
.set(GraphvizAttribute.RANKSEP, 1.2)
170+
.set(GraphvizAttribute.RANKSEP, 0.6)
167171
.set(GraphvizAttribute.BGCOLOR, backgroundColor);
168172

169173
Set<TensorNode> sourceNodes = new HashSet<>();
@@ -200,14 +204,6 @@ private void export() {
200204
exporterForNodeType(node.getType()).exportNode(GraphVisualizer.this, this, node);
201205
}
202206

203-
dotGraph.sameRank(
204-
sourceNodes
205-
.stream()
206-
.map(TensorNode::getId)
207-
.map(Object::toString)
208-
.map(dotGraph::lookup)
209-
.toList()
210-
);
211207
}
212208

213209
public void decoratePrimaryNode(LoomNode loomNode, DotGraph.Node dotNode) {
@@ -224,6 +220,7 @@ public void decoratePrimaryNode(LoomNode loomNode, DotGraph.Node dotNode) {
224220
}
225221

226222
dotNode.set(GraphvizAttribute.XLABEL, HtmlLabel.from(table));
223+
dotNode.set(GraphvizAttribute.PENWIDTH, 2);
227224
}
228225

229226
public DotGraph.Node createPrimaryNode(LoomNode node) {
@@ -276,10 +273,10 @@ public void maybeRenderAnnotations(
276273
dotAnnotationNode.set(GraphvizAttribute.LABEL, HtmlLabel.from(labelTable));
277274

278275
var dotNode = dotGraph.assertLookup(nodeId, DotGraph.Node.class);
279-
var link = dotGraph.createEdge(dotNode, dotAnnotationNode);
280-
link
281-
.set(GraphvizAttribute.ARROWHEAD, "odotodot")
282-
.set(GraphvizAttribute.STYLE, "bold")
276+
dotGraph.createEdge(dotNode, dotAnnotationNode)
277+
.set(GraphvizAttribute.PENWIDTH, 3)
278+
.set(GraphvizAttribute.COLOR, "black:white:white:white:white:white:white:black")
279+
.set(GraphvizAttribute.ARROWHEAD, "none")
283280
.set(GraphvizAttribute.WEIGHT, 5);
284281

285282
dotGraph.sameRank(dotNode, dotAnnotationNode);
@@ -394,6 +391,9 @@ public GH.ElementWrapper<?> jsonToElement(JsonNode node) {
394391
}
395392
}
396393
case ObjectNode object -> {
394+
if (object.isEmpty()) {
395+
return GH.bold("(empty)");
396+
}
397397
return GH
398398
.table()
399399
.border(0)

tensortapestry-loom/src/main/java/org/tensortapestry/loom/graph/export/graphviz/OperationNodeExporter.java

+55-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package org.tensortapestry.loom.graph.export.graphviz;
22

3+
import java.awt.*;
34
import java.util.*;
5+
import java.util.List;
6+
47
import org.tensortapestry.graphviz.DotGraph;
8+
import org.tensortapestry.graphviz.FormatUtils;
59
import org.tensortapestry.graphviz.GraphvizAttribute;
610
import org.tensortapestry.graphviz.HtmlLabel;
711
import org.tensortapestry.loom.graph.LoomNode;
812
import org.tensortapestry.loom.graph.dialects.tensorops.OperationNode;
13+
import org.tensortapestry.loom.graph.dialects.tensorops.TensorOpNodes;
914
import org.tensortapestry.loom.graph.dialects.tensorops.TensorSelection;
1015

1116
public class OperationNodeExporter implements GraphVisualizer.NodeTypeExporter {
@@ -20,14 +25,31 @@ public void exportNode(
2025

2126
var colorScheme = context.colorSchemeForNode(loomNode.getId());
2227

23-
var dotNode = context.createPrimaryNode(loomNode);
28+
var opCluster = context.getDotGraph().createCluster(loomNode.getId() + "_op_cluster");
29+
opCluster
30+
.getAttributes()
31+
.set(GraphvizAttribute.MARGIN, 16)
32+
.set(GraphvizAttribute.PERIPHERIES, 2)
33+
.set(GraphvizAttribute.PENWIDTH, 4)
34+
.set(GraphvizAttribute.STYLE, "rounded");
35+
36+
if (operation.hasAnnotation(TensorOpNodes.IO_SEQUENCE_POINT_TYPE)) {
37+
opCluster
38+
.getAttributes()
39+
.set(GraphvizAttribute.BGCOLOR, "yellow")
40+
.set(GraphvizAttribute.STYLE, "filled, rounded");
41+
// .set(GraphvizAttribute.BGCOLOR, "yellow:black:yellow:black:yellow:black")
42+
// .set(GraphvizAttribute.STYLE, "striped, dashed");
43+
}
44+
45+
var dotNode = opCluster.createNode(loomNode.getId().toString());
46+
context.decoratePrimaryNode(loomNode, dotNode);
2447
dotNode
2548
.set(GraphvizAttribute.SHAPE, "tab")
2649
.set(GraphvizAttribute.STYLE, "filled")
27-
.set(GraphvizAttribute.FILLCOLOR, colorScheme.getKey())
28-
.set(GraphvizAttribute.PENWIDTH, 2);
50+
.set(GraphvizAttribute.FILLCOLOR, colorScheme.getKey());
2951

30-
context.maybeRenderAnnotations(loomNode);
52+
context.maybeRenderAnnotations(loomNode, opCluster);
3153

3254
GH.TableWrapper labelTable = GH
3355
.table()
@@ -39,8 +61,10 @@ public void exportNode(
3961

4062
dotNode.set(GraphvizAttribute.LABEL, HtmlLabel.from(labelTable));
4163

42-
routeNodes(context, operation.getId(), operation.getInputs(), true);
43-
routeNodes(context, operation.getId(), operation.getOutputs(), false);
64+
if (operation.getApplicationNodes().stream().count() > 1) {
65+
routeNodes(context, operation.getId(), operation.getInputs(), true);
66+
routeNodes(context, operation.getId(), operation.getOutputs(), false);
67+
}
4468
}
4569

4670
protected static void routeNodes(
@@ -79,27 +103,47 @@ protected static void routeNodes(
79103
var routeNode = routeCluster.createNode(routeId);
80104
routeNode
81105
.set(GraphvizAttribute.LABEL, "")
82-
.set(GraphvizAttribute.SHAPE, "circle")
83-
.set(GraphvizAttribute.HEIGHT, 0.3)
84-
.set(GraphvizAttribute.WIDTH, 0.3)
106+
.set(GraphvizAttribute.SHAPE, "box")
107+
.set(GraphvizAttribute.HEIGHT, 0.4)
108+
.set(GraphvizAttribute.WIDTH, 0.4)
85109
.set(GraphvizAttribute.STYLE, "filled")
86-
.set(GraphvizAttribute.FILLCOLOR, tensorColor);
110+
.set(GraphvizAttribute.COLOR, tensorColor);
87111

88112
routeNodes.add(routeNode);
89113

90114
DotGraph.Edge routeEdge;
91115
if (isInput) {
92116
routeEdge = dotGraph.createEdge(tensorNode, routeNode);
117+
routeEdge
118+
.set(GraphvizAttribute.HEADCLIP, false);
93119
} else {
94120
routeEdge = dotGraph.createEdge(routeNode, tensorNode);
121+
routeEdge
122+
.set(GraphvizAttribute.TAILCLIP, false);
95123
}
96124

97-
routeEdge.set(GraphvizAttribute.COLOR, tensorColor).set(GraphvizAttribute.PENWIDTH, 12);
125+
routeEdge
126+
.set(GraphvizAttribute.ARROWHEAD, "none")
127+
.set(GraphvizAttribute.COLOR, tensorColor)
128+
.set(GraphvizAttribute.PENWIDTH, 24);
98129
}
99130
}
100131

101132
// Create a grid of route and index nodes to force a diagonal spacing layout.
102133
if (routeNodes.size() > 1) {
134+
for (var id : routedTensors) {
135+
var tensorNode = dotGraph.assertLookup(id.toString(), DotGraph.Node.class);
136+
DotGraph.Edge orderingEdge;
137+
if (isInput) {
138+
orderingEdge = dotGraph.createEdge(tensorNode, routeNodes.get(0));
139+
} else {
140+
orderingEdge = dotGraph.createEdge(routeNodes.get(0), tensorNode);
141+
}
142+
orderingEdge
143+
.set(GraphvizAttribute.STYLE, "invis");
144+
}
145+
146+
103147
List<List<DotGraph.Node>> rows = new ArrayList<>();
104148
for (int r = 0; r < routeNodes.size(); r++) {
105149
var row = new ArrayList<DotGraph.Node>();

tensortapestry-loom/src/main/java/org/tensortapestry/loom/graph/export/graphviz/TensorNodeExporter.java

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ public void exportNode(
2424
.set(GraphvizAttribute.STYLE, "filled")
2525
.set(GraphvizAttribute.FILLCOLOR, colorScheme.getKey())
2626
.set(GraphvizAttribute.GRADIENTANGLE, 315)
27-
.set(GraphvizAttribute.PENWIDTH, 2)
2827
.set(GraphvizAttribute.MARGIN, 0.2);
2928

3029
context.maybeRenderAnnotations(loomNode);

0 commit comments

Comments
 (0)