aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/visualization
diff options
context:
space:
mode:
authorLibravatar nagilooh <ficsorattila96@gmail.com>2023-08-03 16:58:25 +0200
committerLibravatar nagilooh <ficsorattila96@gmail.com>2023-08-03 16:58:25 +0200
commit87fe96f3c67993ab1e4b131d73ecf276482e65a9 (patch)
tree559f9912284b62e53212a83f018ee252dc1f909f /subprojects/visualization
parentMove DSE to new subproject (diff)
downloadrefinery-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')
-rw-r--r--subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java214
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;
4import tools.refinery.store.model.Interpretation; 4import tools.refinery.store.model.Interpretation;
5import tools.refinery.store.model.Model; 5import tools.refinery.store.model.Model;
6import tools.refinery.store.representation.AnySymbol; 6import tools.refinery.store.representation.AnySymbol;
7import tools.refinery.store.representation.TruthValue;
7import tools.refinery.store.tuple.Tuple; 8import tools.refinery.store.tuple.Tuple;
8import tools.refinery.visualization.ModelVisualizerAdapter; 9import tools.refinery.visualization.ModelVisualizerAdapter;
9import tools.refinery.visualization.ModelVisualizerStoreAdapter; 10import tools.refinery.visualization.ModelVisualizerStoreAdapter;
10 11
11import java.io.*; 12import java.io.*;
12import java.util.HashMap; 13import java.util.*;
13import java.util.Map; 14import java.util.stream.Collectors;
14 15
15public class ModelVisualizerAdapterImpl implements ModelVisualizerAdapter { 16public 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");