Skip to content

Commit 10c859b

Browse files
author
Mark Robinson
committed
Dynamic DOT writing for input and output subgraphs
1 parent 1bbd508 commit 10c859b

File tree

4 files changed

+252
-10
lines changed

4 files changed

+252
-10
lines changed

src/main/java/org/commonwl/viewer/domain/Workflow.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@
1919

2020
package org.commonwl.viewer.domain;
2121

22+
import org.commonwl.viewer.services.DotWriter;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
2225
import org.springframework.data.annotation.Id;
2326
import org.springframework.format.annotation.DateTimeFormat;
2427

28+
import java.io.IOException;
29+
import java.io.StringWriter;
2530
import java.nio.file.Path;
2631
import java.util.Date;
2732
import java.util.Map;
@@ -31,6 +36,8 @@
3136
*/
3237
public class Workflow {
3338

39+
static private final Logger logger = LoggerFactory.getLogger(Workflow.class);
40+
3441
// ID for database
3542
@Id
3643
private String id;
@@ -47,11 +54,24 @@ public class Workflow {
4754
private Map<String, CWLElement> inputs;
4855
private Map<String, CWLElement> outputs;
4956

57+
// DOT graph of the contents
58+
private String dotGraph = "test";
59+
5060
public Workflow(String label, String doc, Map<String, CWLElement> inputs, Map<String, CWLElement> outputs) {
5161
this.label = label;
5262
this.doc = doc;
5363
this.inputs = inputs;
5464
this.outputs = outputs;
65+
66+
// Create a DOT graph for this workflow and store it
67+
StringWriter graphWriter = new StringWriter();
68+
DotWriter dotWriter = new DotWriter(graphWriter);
69+
try {
70+
dotWriter.writeGraph(this);
71+
this.dotGraph = graphWriter.toString();
72+
} catch (IOException ex) {
73+
logger.error("Failed to create DOT graph for workflow: " + ex.getMessage());
74+
}
5575
}
5676

5777
@Override
@@ -122,4 +142,12 @@ public Date getRetrievedOn() {
122142
public void setRetrievedOn(Date retrievedOn) {
123143
this.retrievedOn = retrievedOn;
124144
}
145+
146+
public String getDotGraph() {
147+
return dotGraph;
148+
}
149+
150+
public void setDotGraph(String dotGraph) {
151+
this.dotGraph = dotGraph;
152+
}
125153
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.commonwl.viewer.services;
21+
22+
import org.commonwl.viewer.domain.CWLElement;
23+
import org.commonwl.viewer.domain.Workflow;
24+
25+
import java.io.IOException;
26+
import java.io.Writer;
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
import java.util.Map;
30+
31+
/**
32+
* Writes GraphViz DOT files from Workflows
33+
*/
34+
public class DotWriter {
35+
36+
private static final String EOL = System.getProperty("line.separator");
37+
private Writer writer;
38+
39+
public DotWriter(Writer writer) {
40+
this.writer = writer;
41+
}
42+
43+
/**
44+
* Write a graph representing a workflow to the Writer
45+
* @param workflow The workflow to be graphed
46+
* @throws IOException Any errors in writing which may have occurred
47+
*/
48+
public void writeGraph(Workflow workflow) throws IOException {
49+
50+
/**
51+
* DOT graph styling is based on the Apache
52+
* Taverna workflow management system
53+
*/
54+
// Begin graph
55+
writeLine("digraph workflow {");
56+
57+
// Overall graph style
58+
writeLine(" graph [");
59+
writeLine(" bgcolor = \"#EEEEEE\"");
60+
writeLine(" color = \"black\"");
61+
writeLine(" fontsize = \"10\"");
62+
writeLine(" labeljust = \"left\"");
63+
writeLine(" clusterrank = \"local\"");
64+
writeLine(" ranksep = \"0.22\"");
65+
writeLine(" nodesep = \"0.05\"");
66+
writeLine(" ]");
67+
68+
// Overall node style
69+
writeLine(" node [");
70+
writeLine(" fontname = \"Helvetica\"");
71+
writeLine(" fontsize = \"10\"");
72+
writeLine(" fontcolor = \"black\"");
73+
writeLine(" shape = \"record\"");
74+
writeLine(" height = \"0\"");
75+
writeLine(" width = \"0\"");
76+
writeLine(" color = \"black\"");
77+
writeLine(" fillcolor = \"lightgoldenrodyellow\"");
78+
writeLine(" style = \"filled\"");
79+
writeLine(" ];");
80+
81+
// Overall edge style
82+
writeLine(" edge [");
83+
writeLine(" fontname=\"Helvetica\"");
84+
writeLine(" fontsize=\"8\"");
85+
writeLine(" fontcolor=\"black\"");
86+
writeLine(" color=\"black\"");
87+
writeLine(" arrowsize=\"0.7\"");
88+
writeLine(" ];");
89+
90+
// Write inputs as a subgraph
91+
writeInputs(workflow);
92+
93+
// Write outputs as a subgraph
94+
writeOutputs(workflow);
95+
96+
// End graph
97+
writeLine("}");
98+
}
99+
100+
/**
101+
* Writes a set of inputs from a workflow to the Writer
102+
* @param workflow The workflow to get the inputs from
103+
* @throws IOException Any errors in writing which may have occurred
104+
*/
105+
private void writeInputs(Workflow workflow) throws IOException {
106+
// Get inputs from workflow
107+
Map<String, CWLElement> inputs = workflow.getInputs();
108+
109+
// Start of subgraph with styling
110+
writeLine(" subgraph cluster_inputs {");
111+
writeLine(" rank = \"same\";");
112+
writeLine(" style = \"dashed\";");
113+
writeLine(" label = \"Workflow Inputs\";");
114+
115+
// Write each of the inputs as a node
116+
for (Map.Entry<String, CWLElement> input : workflow.getInputs().entrySet()) {
117+
writeInputOutput(input);
118+
}
119+
120+
// End subgraph
121+
writeLine(" }");
122+
}
123+
124+
/**
125+
* Writes a set of outputs from a workflow to the Writer
126+
* @param workflow The workflow to get the outputs from
127+
* @throws IOException Any errors in writing which may have occurred
128+
*/
129+
private void writeOutputs(Workflow workflow) throws IOException {
130+
// Start of subgraph with styling
131+
writeLine(" subgraph cluster_outputs {");
132+
writeLine(" rank = \"same\";");
133+
writeLine(" style = \"dashed\";");
134+
writeLine(" label = \"Workflow Outputs\";");
135+
136+
// Write each of the outputs as a node
137+
for (Map.Entry<String, CWLElement> output : workflow.getOutputs().entrySet()) {
138+
writeInputOutput(output);
139+
}
140+
141+
// End subgraph
142+
writeLine(" }");
143+
}
144+
145+
/**
146+
* Writes a single input or output to the Writer
147+
* @param inputOutput The input or output
148+
* @throws IOException Any errors in writing which may have occurred
149+
*/
150+
private void writeInputOutput(Map.Entry<String, CWLElement> inputOutput) throws IOException {
151+
// List of options for this node
152+
List<String> nodeOptions = new ArrayList<>();
153+
nodeOptions.add("fillcolor=\"#94DDF4\"");
154+
155+
// Use label if it is defined
156+
String label = inputOutput.getValue().getLabel();
157+
if (label != null && !label.isEmpty()) {
158+
nodeOptions.add("label=\"" + label + "\";");
159+
}
160+
161+
// Write the line for the node
162+
writeLine(" \"" + inputOutput.getKey() + "\" [" + String.join(",", nodeOptions) + "];");
163+
}
164+
165+
/**
166+
* Write a single line using the Writer
167+
* @param line The line to be written
168+
* @throws IOException Any errors in writing which may have occurred
169+
*/
170+
private void writeLine(String line) throws IOException {
171+
writer.write(line);
172+
writer.write(EOL);
173+
}
174+
175+
}

src/main/resources/static/css/main.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ body {
5555
stroke-width: 0;
5656
}
5757

58+
#graph .subgraph path {
59+
stroke-dasharray: 1, 5;
60+
}
61+
5862
#loading {
5963
width: 150px;
6064
margin: 0 auto;

src/main/resources/templates/workflow.html

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,54 @@ <h4 class="modal-title" id="dotGraphLabel">Workflow DOT Graph</h4>
4141
<div class="modal-body">
4242
<a id="download-link-gv" href="" download="workflow.gv"></a>
4343
<button id="download-gv" class="btn btn-primary" type="button">Download as .gv File</button>
44-
<textarea id="dot" class="form-control" rows="15">
44+
<textarea id="dot" class="form-control" rows="15" th:field="${workflow.dotGraph}">
4545
digraph G {
46-
bgcolor="#eeeeee";
47-
subgraph cluster_c0 {
48-
a0 -> a1 -> a2 -> a3;
46+
graph [
47+
bgcolor="#eeeeee"
48+
color="black"
49+
fontsize="10"
50+
labeljust="left"
51+
clusterrank="local"
52+
ranksep="0.22"
53+
nodesep="0.05"
54+
];
55+
node [
56+
fontname="Helvetica"
57+
fontsize="10"
58+
fontcolor="black"
59+
shape="record"
60+
height="0"
61+
width="0"
62+
color="black"
63+
fillcolor="lightgoldenrodyellow"
64+
style="filled"
65+
];
66+
edge [
67+
fontname="Helvetica"
68+
fontsize="8"
69+
fontcolor="black"
70+
color="black"
71+
arrowsize="0.7"
72+
];
73+
subgraph cluster_inputs {
74+
rank = same;
75+
style = "dashed";
76+
label = "Workflow Inputs";
77+
input1 [fillcolor="#94DDF4"];
78+
input2 [fillcolor="#94DDF4"];
79+
input3 [fillcolor="#94DDF4"];
4980
}
50-
subgraph cluster_c1 {
51-
b0 -> b1 -> b2 -> b3;
81+
subgraph cluster_outputs {
82+
rank = same;
83+
style = "dotted";
84+
label = "Workflow Outputs";
85+
output1 [fillcolor="#94DDF4"];
86+
output2 [fillcolor="#94DDF4"];
87+
output3 [fillcolor="#94DDF4"];
5288
}
53-
x -> a0;
54-
x -> b0;
55-
a1 -> b3;
56-
b1 -> a3;
89+
input1->step1->step4->output1;
90+
input2->step2->output2;
91+
input3->step3->output3;
5792
}
5893
</textarea>
5994
</div>

0 commit comments

Comments
 (0)