diff options
Diffstat (limited to 'subprojects/store-dse-visualization/src')
9 files changed, 606 insertions, 0 deletions
diff --git a/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java new file mode 100644 index 00000000..ad80bbc6 --- /dev/null +++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java | |||
@@ -0,0 +1,23 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.visualization; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapter; | ||
9 | import tools.refinery.store.map.Version; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | import tools.refinery.visualization.internal.ModelVisualizerBuilderImpl; | ||
12 | import tools.refinery.visualization.statespace.VisualizationStore; | ||
13 | |||
14 | import java.util.Collection; | ||
15 | |||
16 | public interface ModelVisualizerAdapter extends ModelAdapter { | ||
17 | |||
18 | ModelVisualizerStoreAdapter getStoreAdapter(); | ||
19 | static ModelVisualizerBuilder builder() { | ||
20 | return new ModelVisualizerBuilderImpl(); | ||
21 | } | ||
22 | void visualize(VisualizationStore visualizationStore); | ||
23 | } | ||
diff --git a/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java new file mode 100644 index 00000000..1ee41cc3 --- /dev/null +++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.visualization; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapterBuilder; | ||
9 | import tools.refinery.visualization.internal.FileFormat; | ||
10 | |||
11 | public interface ModelVisualizerBuilder extends ModelAdapterBuilder { | ||
12 | ModelVisualizerBuilder withOutputPath(String outputPath); | ||
13 | ModelVisualizerBuilder withFormat(FileFormat format); | ||
14 | ModelVisualizerBuilder saveDesignSpace(); | ||
15 | ModelVisualizerBuilder saveStates(); | ||
16 | } | ||
diff --git a/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java new file mode 100644 index 00000000..46663b2a --- /dev/null +++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.visualization; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelStoreAdapter; | ||
9 | import tools.refinery.visualization.internal.FileFormat; | ||
10 | |||
11 | import java.util.Set; | ||
12 | |||
13 | public interface ModelVisualizerStoreAdapter extends ModelStoreAdapter { | ||
14 | |||
15 | String getOutputPath(); | ||
16 | |||
17 | boolean isRenderDesignSpace(); | ||
18 | |||
19 | boolean isRenderStates(); | ||
20 | |||
21 | Set<FileFormat> getFormats(); | ||
22 | } | ||
diff --git a/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java new file mode 100644 index 00000000..c5dffeb2 --- /dev/null +++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java | |||
@@ -0,0 +1,26 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.visualization.internal; | ||
7 | |||
8 | public enum FileFormat { | ||
9 | BMP("bmp"), | ||
10 | DOT("dot"), | ||
11 | JPEG("jpg"), | ||
12 | PDF("pdf"), | ||
13 | PLAIN("plain"), | ||
14 | PNG("png"), | ||
15 | SVG("svg"); | ||
16 | |||
17 | private final String format; | ||
18 | |||
19 | FileFormat(String format) { | ||
20 | this.format = format; | ||
21 | } | ||
22 | |||
23 | public String getFormat() { | ||
24 | return format; | ||
25 | } | ||
26 | } | ||
diff --git a/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java new file mode 100644 index 00000000..04be22d6 --- /dev/null +++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java | |||
@@ -0,0 +1,60 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.visualization.internal; | ||
7 | |||
8 | import tools.refinery.store.adapter.ModelAdapter; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.model.ModelStore; | ||
11 | import tools.refinery.visualization.ModelVisualizerStoreAdapter; | ||
12 | |||
13 | import java.util.Set; | ||
14 | |||
15 | public class ModelVisualizeStoreAdapterImpl implements ModelVisualizerStoreAdapter { | ||
16 | private final ModelStore store; | ||
17 | private final String outputPath; | ||
18 | private final boolean renderDesignSpace; | ||
19 | private final boolean renderStates; | ||
20 | private final Set<FileFormat> formats; | ||
21 | |||
22 | public ModelVisualizeStoreAdapterImpl(ModelStore store, String outputPath, Set<FileFormat> formats, | ||
23 | boolean renderDesignSpace, boolean renderStates) { | ||
24 | this.store = store; | ||
25 | this.outputPath = outputPath; | ||
26 | this.formats = formats; | ||
27 | this.renderDesignSpace = renderDesignSpace; | ||
28 | this.renderStates = renderStates; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public ModelStore getStore() { | ||
33 | return store; | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public ModelAdapter createModelAdapter(Model model) { | ||
38 | return new ModelVisualizerAdapterImpl(model, this); | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public String getOutputPath() { | ||
43 | return outputPath; | ||
44 | } | ||
45 | |||
46 | @Override | ||
47 | public boolean isRenderDesignSpace() { | ||
48 | return renderDesignSpace; | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public boolean isRenderStates() { | ||
53 | return renderStates; | ||
54 | } | ||
55 | |||
56 | @Override | ||
57 | public Set<FileFormat> getFormats() { | ||
58 | return formats; | ||
59 | } | ||
60 | } | ||
diff --git a/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java new file mode 100644 index 00000000..a6a3dc69 --- /dev/null +++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java | |||
@@ -0,0 +1,335 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.visualization.internal; | ||
7 | |||
8 | import tools.refinery.store.map.Version; | ||
9 | import tools.refinery.store.model.Interpretation; | ||
10 | import tools.refinery.store.model.Model; | ||
11 | import tools.refinery.store.representation.AnySymbol; | ||
12 | import tools.refinery.store.representation.TruthValue; | ||
13 | import tools.refinery.store.tuple.Tuple; | ||
14 | import tools.refinery.visualization.ModelVisualizerAdapter; | ||
15 | import tools.refinery.visualization.ModelVisualizerStoreAdapter; | ||
16 | import tools.refinery.visualization.statespace.VisualizationStore; | ||
17 | |||
18 | import java.io.*; | ||
19 | import java.util.*; | ||
20 | import java.util.stream.Collectors; | ||
21 | |||
22 | public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { | ||
23 | private final Model model; | ||
24 | private final ModelVisualizerStoreAdapter storeAdapter; | ||
25 | private final Map<AnySymbol, Interpretation<?>> allInterpretations; | ||
26 | private final StringBuilder designSpaceBuilder = new StringBuilder(); | ||
27 | private final Map<Version, Integer> states = new HashMap<>(); | ||
28 | private int transitionCounter = 0; | ||
29 | private Integer numberOfStates = 0; | ||
30 | private final String outputPath; | ||
31 | private final Set<FileFormat> formats; | ||
32 | private final boolean renderDesignSpace; | ||
33 | private final boolean renderStates; | ||
34 | |||
35 | private static final Map<Object, String> truthValueToDot = Map.of( | ||
36 | TruthValue.TRUE, "1", | ||
37 | TruthValue.FALSE, "0", | ||
38 | TruthValue.UNKNOWN, "½", | ||
39 | TruthValue.ERROR, "E", | ||
40 | true, "1", | ||
41 | false, "0" | ||
42 | ); | ||
43 | |||
44 | public ModelVisualizerAdapterImpl(Model model, ModelVisualizerStoreAdapter storeAdapter) { | ||
45 | this.model = model; | ||
46 | this.storeAdapter = storeAdapter; | ||
47 | this.outputPath = storeAdapter.getOutputPath(); | ||
48 | this.formats = storeAdapter.getFormats(); | ||
49 | if (formats.isEmpty()) { | ||
50 | formats.add(FileFormat.SVG); | ||
51 | } | ||
52 | this.renderDesignSpace = storeAdapter.isRenderDesignSpace(); | ||
53 | this.renderStates = storeAdapter.isRenderStates(); | ||
54 | |||
55 | this.allInterpretations = new HashMap<>(); | ||
56 | for (var symbol : storeAdapter.getStore().getSymbols()) { | ||
57 | var arity = symbol.arity(); | ||
58 | if (arity < 1 || arity > 2) { | ||
59 | continue; | ||
60 | } | ||
61 | var interpretation = (Interpretation<?>) model.getInterpretation(symbol); | ||
62 | allInterpretations.put(symbol, interpretation); | ||
63 | } | ||
64 | designSpaceBuilder.append("digraph designSpace {\n"); | ||
65 | designSpaceBuilder.append(""" | ||
66 | nodesep=0 | ||
67 | ranksep=5 | ||
68 | node[ | ||
69 | \tstyle=filled | ||
70 | \tfillcolor=white | ||
71 | ] | ||
72 | """); | ||
73 | } | ||
74 | |||
75 | @Override | ||
76 | public Model getModel() { | ||
77 | return model; | ||
78 | } | ||
79 | |||
80 | @Override | ||
81 | public ModelVisualizerStoreAdapter getStoreAdapter() { | ||
82 | return storeAdapter; | ||
83 | } | ||
84 | |||
85 | private String createDotForCurrentModelState() { | ||
86 | |||
87 | var unaryTupleToInterpretationsMap = new HashMap<Tuple, LinkedHashSet<Interpretation<?>>>(); | ||
88 | |||
89 | var sb = new StringBuilder(); | ||
90 | |||
91 | sb.append("digraph model {\n"); | ||
92 | sb.append(""" | ||
93 | node [ | ||
94 | \tstyle="filled, rounded" | ||
95 | \tshape=plain | ||
96 | \tpencolor="#00000088" | ||
97 | \tfontname="Helvetica" | ||
98 | ] | ||
99 | """); | ||
100 | sb.append(""" | ||
101 | edge [ | ||
102 | \tlabeldistance=3 | ||
103 | \tfontname="Helvetica" | ||
104 | ] | ||
105 | """); | ||
106 | |||
107 | for (var entry : allInterpretations.entrySet()) { | ||
108 | var key = entry.getKey(); | ||
109 | var arity = key.arity(); | ||
110 | var cursor = entry.getValue().getAll(); | ||
111 | if (arity == 1) { | ||
112 | while (cursor.move()) { | ||
113 | unaryTupleToInterpretationsMap.computeIfAbsent(cursor.getKey(), k -> new LinkedHashSet<>()) | ||
114 | .add(entry.getValue()); | ||
115 | } | ||
116 | } else if (arity == 2) { | ||
117 | while (cursor.move()) { | ||
118 | var tuple = cursor.getKey(); | ||
119 | for (var i = 0; i < tuple.getSize(); i++) { | ||
120 | var id = tuple.get(i); | ||
121 | unaryTupleToInterpretationsMap.computeIfAbsent(Tuple.of(id), k -> new LinkedHashSet<>()); | ||
122 | } | ||
123 | sb.append(drawEdge(cursor.getKey(), key, entry.getValue())); | ||
124 | } | ||
125 | } | ||
126 | } | ||
127 | for (var entry : unaryTupleToInterpretationsMap.entrySet()) { | ||
128 | sb.append(drawElement(entry)); | ||
129 | } | ||
130 | sb.append("}"); | ||
131 | return sb.toString(); | ||
132 | } | ||
133 | |||
134 | private StringBuilder drawElement(Map.Entry<Tuple, LinkedHashSet<Interpretation<?>>> entry) { | ||
135 | var sb = new StringBuilder(); | ||
136 | |||
137 | var tableStyle = " CELLSPACING=\"0\" BORDER=\"2\" CELLBORDER=\"0\" CELLPADDING=\"4\" STYLE=\"ROUNDED\""; | ||
138 | |||
139 | var key = entry.getKey(); | ||
140 | var id = key.get(0); | ||
141 | var mainLabel = String.valueOf(id); | ||
142 | var interpretations = entry.getValue(); | ||
143 | var backgroundColor = toBackgroundColorString(averageColor(interpretations)); | ||
144 | |||
145 | sb.append(id); | ||
146 | sb.append(" [\n"); | ||
147 | sb.append("\tfillcolor=\"").append(backgroundColor).append("\"\n"); | ||
148 | sb.append("\tlabel="); | ||
149 | if (interpretations.isEmpty()) { | ||
150 | sb.append("<<TABLE").append(tableStyle).append(">\n\t<TR><TD>").append(mainLabel).append("</TD></TR>"); | ||
151 | } | ||
152 | else { | ||
153 | sb.append("<<TABLE").append(tableStyle).append(">\n\t\t<TR><TD COLSPAN=\"3\" BORDER=\"2\" SIDES=\"B\">") | ||
154 | .append(mainLabel).append("</TD></TR>\n"); | ||
155 | for (var interpretation : interpretations) { | ||
156 | var rawValue = interpretation.get(key); | ||
157 | |||
158 | if (rawValue == null || rawValue.equals(TruthValue.FALSE) || rawValue.equals(false)) { | ||
159 | continue; | ||
160 | } | ||
161 | var color = "black"; | ||
162 | if (rawValue.equals(TruthValue.ERROR)) { | ||
163 | color = "red"; | ||
164 | } | ||
165 | var value = truthValueToDot.getOrDefault(rawValue, rawValue.toString()); | ||
166 | var symbol = interpretation.getSymbol(); | ||
167 | |||
168 | if (symbol.valueType() == String.class) { | ||
169 | value = "\"" + value + "\""; | ||
170 | } | ||
171 | sb.append("\t\t<TR><TD><FONT COLOR=\"").append(color).append("\">") | ||
172 | .append(interpretation.getSymbol().name()) | ||
173 | .append("</FONT></TD><TD><FONT COLOR=\"").append(color).append("\">") | ||
174 | .append("=</FONT></TD><TD><FONT COLOR=\"").append(color).append("\">").append(value) | ||
175 | .append("</FONT></TD></TR>\n"); | ||
176 | } | ||
177 | } | ||
178 | sb.append("\t\t</TABLE>>\n"); | ||
179 | sb.append("]\n"); | ||
180 | |||
181 | return sb; | ||
182 | } | ||
183 | |||
184 | private String drawEdge(Tuple edge, AnySymbol symbol, Interpretation<?> interpretation) { | ||
185 | var value = interpretation.get(edge); | ||
186 | |||
187 | if (value == null || value.equals(TruthValue.FALSE) || value.equals(false)) { | ||
188 | return ""; | ||
189 | } | ||
190 | |||
191 | var sb = new StringBuilder(); | ||
192 | var style = "solid"; | ||
193 | var color = "black"; | ||
194 | if (value.equals(TruthValue.UNKNOWN)) { | ||
195 | style = "dotted"; | ||
196 | } | ||
197 | else if (value.equals(TruthValue.ERROR)) { | ||
198 | style = "dashed"; | ||
199 | color = "red"; | ||
200 | } | ||
201 | |||
202 | var from = edge.get(0); | ||
203 | var to = edge.get(1); | ||
204 | var name = symbol.name(); | ||
205 | sb.append(from).append(" -> ").append(to) | ||
206 | .append(" [\n\tstyle=").append(style) | ||
207 | .append("\n\tcolor=").append(color) | ||
208 | .append("\n\tfontcolor=").append(color) | ||
209 | .append("\n\tlabel=\"").append(name) | ||
210 | .append("\"]\n"); | ||
211 | return sb.toString(); | ||
212 | } | ||
213 | |||
214 | private String toBackgroundColorString(Integer[] backgroundColor) { | ||
215 | if (backgroundColor.length == 3) | ||
216 | return String.format("#%02x%02x%02x", backgroundColor[0], backgroundColor[1], backgroundColor[2]); | ||
217 | else if (backgroundColor.length == 4) | ||
218 | return String.format("#%02x%02x%02x%02x", backgroundColor[0], backgroundColor[1], backgroundColor[2], | ||
219 | backgroundColor[3]); | ||
220 | return null; | ||
221 | } | ||
222 | |||
223 | private Integer[] typeColor(String name) { | ||
224 | @SuppressWarnings("squid:S2245") | ||
225 | var random = new Random(name.hashCode()); | ||
226 | return new Integer[] { random.nextInt(128) + 128, random.nextInt(128) + 128, random.nextInt(128) + 128 }; | ||
227 | } | ||
228 | |||
229 | private Integer[] averageColor(Set<Interpretation<?>> interpretations) { | ||
230 | if(interpretations.isEmpty()) { | ||
231 | return new Integer[]{256, 256, 256}; | ||
232 | } | ||
233 | // TODO: Only use interpretations where the value is not false (or unknown) | ||
234 | var symbols = interpretations.stream() | ||
235 | .map(i -> typeColor(i.getSymbol().name())).toArray(Integer[][]::new); | ||
236 | |||
237 | |||
238 | |||
239 | return new Integer[] { | ||
240 | Arrays.stream(symbols).map(i -> i[0]).collect(Collectors.averagingInt(Integer::intValue)).intValue(), | ||
241 | Arrays.stream(symbols).map(i -> i[1]).collect(Collectors.averagingInt(Integer::intValue)).intValue(), | ||
242 | Arrays.stream(symbols).map(i -> i[2]).collect(Collectors.averagingInt(Integer::intValue)).intValue() | ||
243 | }; | ||
244 | } | ||
245 | |||
246 | private String createDotForModelState(Version version) { | ||
247 | var currentVersion = model.getState(); | ||
248 | model.restore(version); | ||
249 | var graph = createDotForCurrentModelState(); | ||
250 | model.restore(currentVersion); | ||
251 | return graph; | ||
252 | } | ||
253 | |||
254 | private boolean saveDot(String dot, String filePath) { | ||
255 | File file = new File(filePath); | ||
256 | file.getParentFile().mkdirs(); | ||
257 | |||
258 | try (FileWriter writer = new FileWriter(file)) { | ||
259 | writer.write(dot); | ||
260 | } catch (Exception e) { | ||
261 | e.printStackTrace(); | ||
262 | return false; | ||
263 | } | ||
264 | return true; | ||
265 | } | ||
266 | |||
267 | private boolean renderDot(String dot, String filePath) { | ||
268 | return renderDot(dot, FileFormat.SVG, filePath); | ||
269 | } | ||
270 | |||
271 | private boolean renderDot(String dot, FileFormat format, String filePath) { | ||
272 | try { | ||
273 | Process process = new ProcessBuilder("dot", "-T" + format.getFormat(), "-o", filePath).start(); | ||
274 | |||
275 | OutputStream osToProcess = process.getOutputStream(); | ||
276 | PrintWriter pwToProcess = new PrintWriter(osToProcess); | ||
277 | pwToProcess.write(dot); | ||
278 | pwToProcess.close(); | ||
279 | } catch (IOException e) { | ||
280 | e.printStackTrace(); | ||
281 | return false; | ||
282 | } | ||
283 | return true; | ||
284 | } | ||
285 | |||
286 | private String buildDesignSpaceDot() { | ||
287 | designSpaceBuilder.append("}"); | ||
288 | return designSpaceBuilder.toString(); | ||
289 | } | ||
290 | |||
291 | private boolean saveDesignSpace(String path) { | ||
292 | saveDot(buildDesignSpaceDot(), path + "/designSpace.dot"); | ||
293 | for (var entry : states.entrySet()) { | ||
294 | saveDot(createDotForModelState(entry.getKey()), path + "/" + entry.getValue() + ".dot"); | ||
295 | } | ||
296 | return true; | ||
297 | } | ||
298 | |||
299 | private void renderDesignSpace(String path, Set<FileFormat> formats) { | ||
300 | File filePath = new File(path); | ||
301 | filePath.mkdirs(); | ||
302 | if (renderStates) { | ||
303 | for (var entry : states.entrySet()) { | ||
304 | var stateId = entry.getValue(); | ||
305 | var stateDot = createDotForModelState(entry.getKey()); | ||
306 | for (var format : formats) { | ||
307 | if (format == FileFormat.DOT) { | ||
308 | saveDot(stateDot, path + "/" + stateId + ".dot"); | ||
309 | } | ||
310 | else { | ||
311 | renderDot(stateDot, format, path + "/" + stateId + "." + format.getFormat()); | ||
312 | } | ||
313 | } | ||
314 | } | ||
315 | } | ||
316 | if (renderDesignSpace) { | ||
317 | var designSpaceDot = buildDesignSpaceDot(); | ||
318 | for (var format : formats) { | ||
319 | if (format == FileFormat.DOT) { | ||
320 | saveDot(designSpaceDot, path + "/designSpace.dot"); | ||
321 | } | ||
322 | else { | ||
323 | renderDot(designSpaceDot, format, path + "/designSpace." + format.getFormat()); | ||
324 | } | ||
325 | } | ||
326 | } | ||
327 | } | ||
328 | |||
329 | @Override | ||
330 | public void visualize(VisualizationStore visualizationStore) { | ||
331 | this.designSpaceBuilder.append(visualizationStore.getDesignSpaceStringBuilder()); | ||
332 | this.states.putAll(visualizationStore.getStates()); | ||
333 | renderDesignSpace(outputPath, formats); | ||
334 | } | ||
335 | } | ||
diff --git a/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java new file mode 100644 index 00000000..9ba2abe8 --- /dev/null +++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java | |||
@@ -0,0 +1,55 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.visualization.internal; | ||
7 | |||
8 | import tools.refinery.store.adapter.AbstractModelAdapterBuilder; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.visualization.ModelVisualizerBuilder; | ||
11 | |||
12 | import java.util.LinkedHashSet; | ||
13 | import java.util.Set; | ||
14 | |||
15 | public class ModelVisualizerBuilderImpl | ||
16 | extends AbstractModelAdapterBuilder<ModelVisualizeStoreAdapterImpl> | ||
17 | implements ModelVisualizerBuilder { | ||
18 | private String outputPath; | ||
19 | private boolean saveDesignSpace = false; | ||
20 | private boolean saveStates = false; | ||
21 | private final Set<FileFormat> formats = new LinkedHashSet<>(); | ||
22 | |||
23 | @Override | ||
24 | protected ModelVisualizeStoreAdapterImpl doBuild(ModelStore store) { | ||
25 | return new ModelVisualizeStoreAdapterImpl(store, outputPath, formats, saveDesignSpace, saveStates); | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public ModelVisualizerBuilder withOutputPath(String outputPath) { | ||
30 | checkNotConfigured(); | ||
31 | this.outputPath = outputPath; | ||
32 | return this; | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public ModelVisualizerBuilder withFormat(FileFormat format) { | ||
37 | checkNotConfigured(); | ||
38 | this.formats.add(format); | ||
39 | return this; | ||
40 | } | ||
41 | |||
42 | @Override | ||
43 | public ModelVisualizerBuilder saveDesignSpace() { | ||
44 | checkNotConfigured(); | ||
45 | this.saveDesignSpace = true; | ||
46 | return this; | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public ModelVisualizerBuilder saveStates() { | ||
51 | checkNotConfigured(); | ||
52 | this.saveStates = true; | ||
53 | return this; | ||
54 | } | ||
55 | } | ||
diff --git a/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/VisualizationStore.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/VisualizationStore.java new file mode 100644 index 00000000..414ef737 --- /dev/null +++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/VisualizationStore.java | |||
@@ -0,0 +1,18 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.visualization.statespace; | ||
7 | |||
8 | import tools.refinery.store.map.Version; | ||
9 | |||
10 | import java.util.Map; | ||
11 | |||
12 | public interface VisualizationStore { | ||
13 | void addState(Version state, String label); | ||
14 | void addSolution(Version state); | ||
15 | void addTransition(Version from, Version to, String label); | ||
16 | StringBuilder getDesignSpaceStringBuilder(); | ||
17 | Map<Version, Integer> getStates(); | ||
18 | } | ||
diff --git a/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/internal/VisualizationStoreImpl.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/internal/VisualizationStoreImpl.java new file mode 100644 index 00000000..08a69cb4 --- /dev/null +++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/internal/VisualizationStoreImpl.java | |||
@@ -0,0 +1,51 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.visualization.statespace.internal; | ||
7 | |||
8 | import tools.refinery.store.map.Version; | ||
9 | import tools.refinery.visualization.statespace.VisualizationStore; | ||
10 | |||
11 | import java.util.HashMap; | ||
12 | import java.util.Map; | ||
13 | |||
14 | public class VisualizationStoreImpl implements VisualizationStore { | ||
15 | |||
16 | private final Map<Version, Integer> states = new HashMap<>(); | ||
17 | private int transitionCounter = 0; | ||
18 | private Integer numberOfStates = 0; | ||
19 | private final StringBuilder designSpaceBuilder = new StringBuilder(); | ||
20 | |||
21 | @Override | ||
22 | public synchronized void addState(Version state, String label) { | ||
23 | if (states.containsKey(state)) { | ||
24 | return; | ||
25 | } | ||
26 | states.put(state, numberOfStates++); | ||
27 | designSpaceBuilder.append(states.get(state)).append(" [label = \"").append(states.get(state)).append(" ("); | ||
28 | designSpaceBuilder.append(label); | ||
29 | designSpaceBuilder.append(")\"\n").append("URL=\"./").append(states.get(state)).append(".svg\"]\n"); | ||
30 | } | ||
31 | |||
32 | @Override | ||
33 | public synchronized void addSolution(Version state) { | ||
34 | designSpaceBuilder.append(states.get(state)).append(" [peripheries = 2]\n"); | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | public synchronized void addTransition(Version from, Version to, String label) { | ||
39 | designSpaceBuilder.append(states.get(from)).append(" -> ").append(states.get(to)) | ||
40 | .append(" [label=\"").append(transitionCounter++).append(": ").append(label).append("\"]\n"); | ||
41 | } | ||
42 | |||
43 | public synchronized StringBuilder getDesignSpaceStringBuilder() { | ||
44 | return designSpaceBuilder; | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | public Map<Version, Integer> getStates() { | ||
49 | return states; | ||
50 | } | ||
51 | } | ||