diff options
author | nagilooh <ficsorattila96@gmail.com> | 2023-08-03 16:58:25 +0200 |
---|---|---|
committer | nagilooh <ficsorattila96@gmail.com> | 2023-08-03 16:58:25 +0200 |
commit | 87fe96f3c67993ab1e4b131d73ecf276482e65a9 (patch) | |
tree | 559f9912284b62e53212a83f018ee252dc1f909f /subprojects/visualization/src/main/java | |
parent | Move DSE to new subproject (diff) | |
download | refinery-87fe96f3c67993ab1e4b131d73ecf276482e65a9.tar.gz refinery-87fe96f3c67993ab1e4b131d73ecf276482e65a9.tar.zst refinery-87fe96f3c67993ab1e4b131d73ecf276482e65a9.zip |
Improve visualization
-Display values from all relevant interpretations
-Support TruthValue
-Add tabular formatting
-Add colors
Diffstat (limited to 'subprojects/visualization/src/main/java')
-rw-r--r-- | subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java | 214 |
1 files changed, 178 insertions, 36 deletions
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java b/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java index 33c5a43b..8555da5f 100644 --- a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java +++ b/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java | |||
@@ -4,13 +4,14 @@ import tools.refinery.store.map.Version; | |||
4 | import tools.refinery.store.model.Interpretation; | 4 | import tools.refinery.store.model.Interpretation; |
5 | import tools.refinery.store.model.Model; | 5 | import tools.refinery.store.model.Model; |
6 | import tools.refinery.store.representation.AnySymbol; | 6 | import tools.refinery.store.representation.AnySymbol; |
7 | import tools.refinery.store.representation.TruthValue; | ||
7 | import tools.refinery.store.tuple.Tuple; | 8 | import tools.refinery.store.tuple.Tuple; |
8 | import tools.refinery.visualization.ModelVisualizerAdapter; | 9 | import tools.refinery.visualization.ModelVisualizerAdapter; |
9 | import tools.refinery.visualization.ModelVisualizerStoreAdapter; | 10 | import tools.refinery.visualization.ModelVisualizerStoreAdapter; |
10 | 11 | ||
11 | import java.io.*; | 12 | import java.io.*; |
12 | import java.util.HashMap; | 13 | import java.util.*; |
13 | import java.util.Map; | 14 | import java.util.stream.Collectors; |
14 | 15 | ||
15 | public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { | 16 | public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { |
16 | private final Model model; | 17 | private final Model model; |
@@ -20,6 +21,15 @@ public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { | |||
20 | private final Map<Version, Integer> states = new HashMap<>(); | 21 | private final Map<Version, Integer> states = new HashMap<>(); |
21 | private int transitionCounter = 0; | 22 | private int transitionCounter = 0; |
22 | private Integer numberOfStates = 0; | 23 | private Integer numberOfStates = 0; |
24 | private static final Map<Object, String> truthValueToDot = new HashMap<>() | ||
25 | {{ | ||
26 | put(TruthValue.TRUE, "1"); | ||
27 | put(TruthValue.FALSE, "0"); | ||
28 | put(TruthValue.UNKNOWN, "½"); | ||
29 | put(TruthValue.ERROR, "E"); | ||
30 | put(true, "1"); | ||
31 | put(false, "0"); | ||
32 | }}; | ||
23 | 33 | ||
24 | public ModelVisualizerAdapterImpl(Model model, ModelVisualizerStoreAdapter storeAdapter) { | 34 | public ModelVisualizerAdapterImpl(Model model, ModelVisualizerStoreAdapter storeAdapter) { |
25 | this.model = model; | 35 | this.model = model; |
@@ -30,22 +40,16 @@ public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { | |||
30 | if (arity < 1 || arity > 2) { | 40 | if (arity < 1 || arity > 2) { |
31 | continue; | 41 | continue; |
32 | } | 42 | } |
33 | var interpretation = model.getInterpretation(symbol); | 43 | var interpretation = (Interpretation<?>) model.getInterpretation(symbol); |
34 | var valueType = symbol.valueType(); | 44 | interpretations.put(symbol, interpretation); |
35 | Interpretation<?> castInterpretation; | ||
36 | if (valueType == Boolean.class) { | ||
37 | castInterpretation = (Interpretation<Boolean>) interpretation; | ||
38 | } | ||
39 | // TODO: support TruthValue | ||
40 | // else if (valueType == TruthValue.class) { | ||
41 | // castInterpretation = (Interpretation<TruthValue>) interpretation; | ||
42 | // } | ||
43 | else { | ||
44 | continue; | ||
45 | } | ||
46 | interpretations.put(symbol, castInterpretation); | ||
47 | } | 45 | } |
48 | designSpaceBuilder.append("digraph designSpace {\n"); | 46 | designSpaceBuilder.append("digraph designSpace {\n"); |
47 | designSpaceBuilder.append(""" | ||
48 | node[ | ||
49 | style=filled | ||
50 | fillcolor=white | ||
51 | ] | ||
52 | """); | ||
49 | } | 53 | } |
50 | 54 | ||
51 | @Override | 55 | @Override |
@@ -60,29 +64,165 @@ public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { | |||
60 | 64 | ||
61 | @Override | 65 | @Override |
62 | public String createDotForCurrentModelState() { | 66 | public String createDotForCurrentModelState() { |
67 | |||
68 | var unaryTupleToInterpretationsMap = new HashMap<Tuple, LinkedHashSet<Interpretation<?>>>(); | ||
69 | |||
63 | var sb = new StringBuilder(); | 70 | var sb = new StringBuilder(); |
71 | |||
64 | sb.append("digraph model {\n"); | 72 | sb.append("digraph model {\n"); |
73 | sb.append(""" | ||
74 | node [ | ||
75 | \tstyle="filled, rounded" | ||
76 | \tshape=plain | ||
77 | \tpencolor="#00000088" | ||
78 | \tfontname="Helvetica" | ||
79 | ] | ||
80 | """); | ||
81 | sb.append(""" | ||
82 | edge [ | ||
83 | \tlabeldistance=3 | ||
84 | \tfontname="Helvetica" | ||
85 | ] | ||
86 | """); | ||
87 | |||
65 | for (var entry : interpretations.entrySet()) { | 88 | for (var entry : interpretations.entrySet()) { |
66 | var key = entry.getKey(); | 89 | var key = entry.getKey(); |
67 | var arity = key.arity(); | 90 | var arity = key.arity(); |
68 | var cursor = entry.getValue().getAll(); | 91 | var cursor = entry.getValue().getAll(); |
69 | while (cursor.move()) { | 92 | if (arity == 1) { |
70 | if (arity == 1) { | 93 | while (cursor.move()) { |
71 | var id = cursor.getKey().get(0); | 94 | unaryTupleToInterpretationsMap.computeIfAbsent(cursor.getKey(), k -> new LinkedHashSet<>()) |
72 | sb.append("\t").append(id).append(" [label=\"").append(key.name()).append(": ").append(id) | 95 | .add(entry.getValue()); |
73 | .append("\"]\n"); | 96 | } |
74 | } else { | 97 | } else if (arity == 2) { |
75 | var from = cursor.getKey().get(0); | 98 | while (cursor.move()) { |
76 | var to = cursor.getKey().get(1); | 99 | var tuple = cursor.getKey(); |
77 | sb.append("\t").append(from).append(" -> ").append(to).append(" [label=\"").append(key.name()) | 100 | for (var i = 0; i < tuple.getSize(); i++) { |
78 | .append("\"]\n"); | 101 | var id = tuple.get(i); |
102 | unaryTupleToInterpretationsMap.computeIfAbsent(Tuple.of(id), k -> new LinkedHashSet<>()); | ||
103 | } | ||
104 | sb.append(drawEdge(cursor.getKey(), key, entry.getValue())); | ||
79 | } | 105 | } |
80 | } | 106 | } |
81 | } | 107 | } |
108 | for (var entry : unaryTupleToInterpretationsMap.entrySet()) { | ||
109 | sb.append(drawElement(entry)); | ||
110 | } | ||
82 | sb.append("}"); | 111 | sb.append("}"); |
83 | return sb.toString(); | 112 | return sb.toString(); |
84 | } | 113 | } |
85 | 114 | ||
115 | private StringBuilder drawElement(Map.Entry<Tuple, LinkedHashSet<Interpretation<?>>> entry) { | ||
116 | var sb = new StringBuilder(); | ||
117 | |||
118 | var tableStyle = " CELLSPACING=\"0\" BORDER=\"2\" CELLBORDER=\"0\" CELLPADDING=\"4\" STYLE=\"ROUNDED\""; | ||
119 | |||
120 | var key = entry.getKey(); | ||
121 | var id = key.get(0); | ||
122 | var mainLabel = String.valueOf(id); | ||
123 | var interpretations = entry.getValue(); | ||
124 | var backgroundColor = toBackgroundColorString(averageColor(interpretations)); | ||
125 | |||
126 | sb.append(id); | ||
127 | sb.append(" [\n"); | ||
128 | sb.append("\tfillcolor=\"").append(backgroundColor).append("\"\n"); | ||
129 | sb.append("\tlabel="); | ||
130 | if (interpretations.isEmpty()) { | ||
131 | sb.append("<<TABLE").append(tableStyle).append(">\n\t<TR><TD>").append(mainLabel).append("</TD></TR>"); | ||
132 | } | ||
133 | else { | ||
134 | sb.append("<<TABLE").append(tableStyle).append(">\n\t\t<TR><TD COLSPAN=\"3\" BORDER=\"2\" SIDES=\"B\">") | ||
135 | .append(mainLabel).append("</TD></TR>\n"); | ||
136 | for (var interpretation : interpretations) { | ||
137 | var rawValue = interpretation.get(key); | ||
138 | |||
139 | if (rawValue == null || rawValue.equals(TruthValue.FALSE) || rawValue.equals(false)) { | ||
140 | continue; | ||
141 | } | ||
142 | var color = "black"; | ||
143 | if (rawValue.equals(TruthValue.ERROR)) { | ||
144 | color = "red"; | ||
145 | } | ||
146 | var value = truthValueToDot.getOrDefault(rawValue, rawValue.toString()); | ||
147 | var symbol = interpretation.getSymbol(); | ||
148 | |||
149 | if (symbol.valueType() == String.class) { | ||
150 | value = "\"" + value + "\""; | ||
151 | } | ||
152 | sb.append("\t\t<TR><TD><FONT COLOR=\"").append(color).append("\">") | ||
153 | .append(interpretation.getSymbol().name()) | ||
154 | .append("</FONT></TD><TD><FONT COLOR=\"").append(color).append("\">") | ||
155 | .append("=</FONT></TD><TD><FONT COLOR=\"").append(color).append("\">").append(value) | ||
156 | .append("</FONT></TD></TR>\n"); | ||
157 | } | ||
158 | } | ||
159 | sb.append("\t\t</TABLE>>\n"); | ||
160 | sb.append("]\n"); | ||
161 | |||
162 | return sb; | ||
163 | } | ||
164 | |||
165 | private String drawEdge(Tuple edge, AnySymbol symbol, Interpretation<?> interpretation) { | ||
166 | var value = interpretation.get(edge); | ||
167 | |||
168 | if (value == null || value.equals(TruthValue.FALSE) || value.equals(false)) { | ||
169 | return ""; | ||
170 | } | ||
171 | |||
172 | var sb = new StringBuilder(); | ||
173 | var style = "solid"; | ||
174 | var color = "black"; | ||
175 | if (value.equals(TruthValue.UNKNOWN)) { | ||
176 | style = "dotted"; | ||
177 | } | ||
178 | else if (value.equals(TruthValue.ERROR)) { | ||
179 | style = "dashed"; | ||
180 | color = "red"; | ||
181 | } | ||
182 | |||
183 | var from = edge.get(0); | ||
184 | var to = edge.get(1); | ||
185 | var name = symbol.name(); | ||
186 | sb.append(from).append(" -> ").append(to) | ||
187 | .append(" [\n\tstyle=").append(style) | ||
188 | .append("\n\tcolor=").append(color) | ||
189 | .append("\n\tfontcolor=").append(color) | ||
190 | .append("\n\tlabel=\"").append(name) | ||
191 | .append("\"]\n"); | ||
192 | return sb.toString(); | ||
193 | } | ||
194 | |||
195 | private String toBackgroundColorString(Integer[] backgroundColor) { | ||
196 | if (backgroundColor.length == 3) | ||
197 | return String.format("#%02x%02x%02x", backgroundColor[0], backgroundColor[1], backgroundColor[2]); | ||
198 | else if (backgroundColor.length == 4) | ||
199 | return String.format("#%02x%02x%02x%02x", backgroundColor[0], backgroundColor[1], backgroundColor[2], | ||
200 | backgroundColor[3]); | ||
201 | return null; | ||
202 | } | ||
203 | |||
204 | private Integer[] typePredicateColor(String name) { | ||
205 | var random = new Random(name.hashCode()); | ||
206 | return new Integer[] { random.nextInt(128) + 128, random.nextInt(128) + 128, random.nextInt(128) + 128 }; | ||
207 | } | ||
208 | |||
209 | private Integer[] averageColor(Set<Interpretation<?>> interpretations) { | ||
210 | if(interpretations.isEmpty()) { | ||
211 | return new Integer[]{256, 256, 256}; | ||
212 | } | ||
213 | // TODO: Only use interpretations where the value is not false (or unknown) | ||
214 | var symbols = interpretations.stream() | ||
215 | .map(i -> typePredicateColor(i.getSymbol().name())).toArray(Integer[][]::new); | ||
216 | |||
217 | |||
218 | |||
219 | return new Integer[] { | ||
220 | Arrays.stream(symbols).map(i -> i[0]).collect(Collectors.averagingInt(Integer::intValue)).intValue(), | ||
221 | Arrays.stream(symbols).map(i -> i[1]).collect(Collectors.averagingInt(Integer::intValue)).intValue(), | ||
222 | Arrays.stream(symbols).map(i -> i[2]).collect(Collectors.averagingInt(Integer::intValue)).intValue() | ||
223 | }; | ||
224 | } | ||
225 | |||
86 | @Override | 226 | @Override |
87 | public String createDotForModelState(Version version) { | 227 | public String createDotForModelState(Version version) { |
88 | var currentVersion = model.getState(); | 228 | var currentVersion = model.getState(); |
@@ -129,15 +269,14 @@ public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { | |||
129 | 269 | ||
130 | @Override | 270 | @Override |
131 | public void addTransition(Version from, Version to, String action) { | 271 | public void addTransition(Version from, Version to, String action) { |
132 | designSpaceBuilder.append(states.get(from)).append(" -> ").append(states.get(to)).append(" [label=\"") | 272 | designSpaceBuilder.append(states.get(from)).append(" -> ").append(states.get(to)) |
133 | .append(transitionCounter++).append(": ").append(action).append("\"]\n"); | 273 | .append(" [label=\"").append(transitionCounter++).append(": ").append(action).append("\"]\n"); |
134 | |||
135 | } | 274 | } |
136 | 275 | ||
137 | @Override | 276 | @Override |
138 | public void addTransition(Version from, Version to, String action, Tuple activation) { | 277 | public void addTransition(Version from, Version to, String action, Tuple activation) { |
139 | designSpaceBuilder.append(states.get(from)).append(" -> ").append(states.get(to)).append(" [label=\"").append(transitionCounter++) | 278 | designSpaceBuilder.append(states.get(from)).append(" -> ").append(states.get(to)) |
140 | .append(": ").append(action).append(" / "); | 279 | .append(" [label=\"").append(transitionCounter++).append(": ").append(action).append(" / "); |
141 | 280 | ||
142 | 281 | ||
143 | for (int i = 0; i < activation.getSize(); i++) { | 282 | for (int i = 0; i < activation.getSize(); i++) { |
@@ -151,12 +290,16 @@ public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { | |||
151 | 290 | ||
152 | @Override | 291 | @Override |
153 | public void addState(Version state) { | 292 | public void addState(Version state) { |
293 | if (states.containsKey(state)) { | ||
294 | return; | ||
295 | } | ||
154 | states.put(state, numberOfStates++); | 296 | states.put(state, numberOfStates++); |
155 | designSpaceBuilder.append(states.get(state)).append(" [URL=\"./").append(states.get(state)).append(".svg\"]\n"); | 297 | designSpaceBuilder.append(states.get(state)).append(" [URL=\"./").append(states.get(state)).append(".svg\"]\n"); |
156 | } | 298 | } |
157 | 299 | ||
158 | @Override | 300 | @Override |
159 | public void addSolution(Version state) { | 301 | public void addSolution(Version state) { |
302 | addState(state); | ||
160 | designSpaceBuilder.append(states.get(state)).append(" [shape = doublecircle]\n"); | 303 | designSpaceBuilder.append(states.get(state)).append(" [shape = doublecircle]\n"); |
161 | } | 304 | } |
162 | 305 | ||
@@ -168,8 +311,8 @@ public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { | |||
168 | @Override | 311 | @Override |
169 | public boolean saveDesignSpace(String path) { | 312 | public boolean saveDesignSpace(String path) { |
170 | saveDot(buildDesignSpaceDot(), path + "/designSpace.dot"); | 313 | saveDot(buildDesignSpaceDot(), path + "/designSpace.dot"); |
171 | for (var state : states.keySet()) { | 314 | for (var entry : states.entrySet()) { |
172 | saveDot(createDotForModelState(state), path + "/" + states.get(state) + ".dot"); | 315 | saveDot(createDotForModelState(entry.getKey()), path + "/" + entry.getValue() + ".dot"); |
173 | } | 316 | } |
174 | return true; | 317 | return true; |
175 | } | 318 | } |
@@ -182,11 +325,10 @@ public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { | |||
182 | @Override | 325 | @Override |
183 | public boolean renderDesignSpace(String path, FileFormat format) { | 326 | public boolean renderDesignSpace(String path, FileFormat format) { |
184 | for (var entry : states.entrySet()) { | 327 | for (var entry : states.entrySet()) { |
185 | var state = entry.getKey(); | ||
186 | var stateId = entry.getValue(); | 328 | var stateId = entry.getValue(); |
187 | var stateDot = createDotForModelState(state); | 329 | var stateDot = createDotForModelState(entry.getKey()); |
188 | saveDot(stateDot, path + "/" + stateId + ".dot"); | 330 | saveDot(stateDot, path + "/" + stateId + ".dot"); |
189 | renderDot(stateDot, path + "/" + stateId + "." + format.getFormat()); | 331 | renderDot(stateDot, format, path + "/" + stateId + "." + format.getFormat()); |
190 | } | 332 | } |
191 | var designSpaceDot = buildDesignSpaceDot(); | 333 | var designSpaceDot = buildDesignSpaceDot(); |
192 | saveDot(designSpaceDot, path + "/designSpace.dot"); | 334 | saveDot(designSpaceDot, path + "/designSpace.dot"); |