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