source: src/main/webapp/plotlog.xhtml@ 22

Last change on this file since 22 was 22, checked in by bart, 4 years ago

Voting requests now contain Offers. Fixed windows whitespace issue. Partiesserver now supports up to 8 parties simultaneously.

File size: 12.8 KB
Line 
1<?xml version="1.0" encoding="UTF-8"?>
2<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3<head>
4<title>Plot log results</title>
5<script src="Chart.min_2.8.0.js"></script>
6<script src="utils.js"></script>
7</head>
8<style>
9canvas {
10 -moz-user-select: none;
11 -webkit-user-select: none;
12 -ms-user-select: none;
13}
14</style>
15<body>
16 <div class="noscript">
17 <h2 style="color: #ff0000">Seems your browser doesn't support
18 Javascript! Websockets rely on Javascript being enabled. Please
19 enable Javascript and reload this page!</h2>
20 </div>
21 <h1 id="header">Graph of log results</h1>
22
23 Progress:
24 <div id="progress">Waiting for log file name</div>
25 <br />
26 <div>Notice, the plotted utilities do not include possible
27 elicitation costs.</div>
28 <div style="width: 100%;">
29 <canvas id="canvas"></canvas>
30 </div>
31
32 Agreement:
33 <div id="agreement" style="display: inline;">?</div>
34</body>
35
36<!-- Script to get/update the SESSIONS list and put it into the table. -->
37<script type="application/javascript">
38
39
40
41
42
43
44
45
46
47
48
49 <![CDATA[
50 "use strict";
51
52
53
54 var ws = null;
55
56 <!-- Most functions are reactive programming written out-->
57
58 function connect() {
59 var query = new URLSearchParams(location.search);
60 var id=query.get('id');
61 if (id==undefined) {
62 alert("missing ?id=<logfile> in URL");
63 return;
64 }
65
66 // load the log file with given id
67 document.getElementById('header').innerHTML="Graph of log "+id;
68
69 var url = "log/"+id+".json";
70 setStatus("Downloading file "+url);
71 var request=new XMLHttpRequest();
72 request.responseType = 'text';
73 request.open('Get', url)
74 request.onload=function() {
75 if (request.status!=200) {
76 setStatus("Failed to fetch "+url+":" +request.status+" "+request.statusText)
77 return;
78 }
79 processLogFile(JSON.parse(request.response));
80 }
81 request.send();
82 }
83
84
85 /**
86 Figure out the protocol contents and parse accordingly.
87 */
88 function processLogFile(json) {
89
90 if (json['SAOPState']!=undefined) {
91 processStandard(json['SAOPState'], 'SAOP');
92 } else if (json['SHAOPState']!=undefined) {
93 processSHAOP(json['SHAOPState']);
94 } else if (json['MOPACState']!=undefined) {
95 // just use the SAOP processor, which shows the
96 // utilities of all Offers in sequence.
97 processStandard(json['MOPACState'], 'MOPAC');
98 } else{
99 setStatus("Unknown log file contents "+Object.keys(json));
100 }
101 }
102
103 /**
104 Handle standard protocol result.
105 @param protocol either SAOP or MOPAC or another SAOP-like protocol
106 */
107 function processStandard(json, protocol) {
108 var partyprofiles={}
109 var profs=json['partyprofiles'];
110 for (var party in profs) {
111 partyprofiles[party]=withoutPartial(profs[party]['profile']);
112 }
113
114 getProfiles(json, partyprofiles, protocol);
115 }
116
117
118 /**
119 Handle SHAOP protocol result. Get only SHAOP profiles. Remove all 'partial=XX' from relevant profiles.
120 */
121 function processSHAOP(json) {
122 var partyprofiles={}
123 var teams=json['settings']['SHAOPSettings']['participants'];
124 for (var partyid in json['partyNumbers']) {
125 var nr= json['partyNumbers'][partyid];
126 if ( (nr & 1 != 0)) continue; // skip COB parties
127 var shaop=teams[nr/2]['shaop'];
128 var profile = withoutPartial(shaop['profile']); // hacky, remove complete query.
129 partyprofiles[partyid]=profile;
130 }
131
132
133 getProfiles(json, partyprofiles, "SHAOP");
134 }
135
136
137 /**
138 @param uristr a string with an uri that may have ?partial=XX
139 @return the given uristr without the "partia=XXl" value
140 */
141 function withoutPartial(uristr) {
142 var profuri=new URL(uristr);
143 var searchParams = new URLSearchParams(profuri.search);
144 searchParams.delete("partial");
145 profuri.search = searchParams.toString();
146 return profuri.toString();
147
148 }
149
150 /**
151 Get the partyprofiles.
152 @param json the json protocol result
153 @param partyprofileURIs dict with the websocket addresses for each party'as profile
154 @param partyprofiles dict with each party's profile. Initially should be {}
155 @param protocol protocolname, eg "SAOP"
156 */
157 function getProfiles(json, partyprofileURIs, protocol, partyprofiles={} ) {
158 var keys = Object.keys(partyprofileURIs);
159 if (keys.length==0) {
160 plotResults(computeUtils(json, partyprofiles), isAgreement(json, protocol));
161 return;
162 }
163 var party = keys[0];
164 var uri=partyprofileURIs[party];
165 delete partyprofileURIs[party];
166
167 setStatus("fetching profile "+uri);
168 if ('WebSocket' in window) {
169 ws = new WebSocket(uri);
170 } else if ('MozWebSocket' in window) {
171 ws = new MozWebSocket(uri);
172 } else {
173 setStatus('Fatal: WebSocket is not supported by this browser. Please use a newer browser');
174 return;
175 }
176 ws.onmessage = function (event) {
177 var profile = JSON.parse(event.data);
178 if (profile['LinearAdditiveUtilitySpace']==undefined) {
179 setStatus('Fatal: profile '+uri+" does not contain a LinearAdditiveUtilitySpace.");
180 return;
181 }
182 partyprofiles[party]=profile['LinearAdditiveUtilitySpace'];
183 getProfiles(json, partyprofileURIs, protocol, partyprofiles);
184 };
185 ws.onerror=function(event) {
186 setStatus('Error fetching profile '+uri+':'+event);
187 }
188 }
189
190 /**
191 @param logfile the logfile content, but below the protocol header
192 @param protocol the protocol name, eg SAOP, SHAOP, MOPAC
193 @return 'Yes' if there is agreement, 'No' if there is no agreement at all,
194 or 'Partial' if part of the parties agreed and part did not.
195 or '?' if unsupported protocol
196 */
197 function isAgreement(logfile, protocol) {
198 switch (protocol) {
199 case "SAOP":
200 case "SHAOP":
201 var actions=logfile['actions'];
202 var lastact = actions[actions.length -1];
203 return 'accept' in lastact ? 'Yes':'No';
204 case "MOPAC":
205 var finalstate=logfile['phase'][Object.keys(logfile['phase'])[0]]['partyStates'];
206 var nagrees=Object.keys(finalstate['agreements']).length;
207 var nparties=Object.keys(finalstate['powers']).length;
208 if (nagrees==nparties) return "Yes";
209 if (nagrees==0) return "No"
210 return "Partial";
211 default:
212 return "?";
213 }
214 }
215 /**
216 @json the log file contents
217 @param partyprofiles a dict with key=party id and value a the (linear additive) profile
218 */
219 function computeUtils(json, partyprofiles) {
220
221 setStatus("Computing utilities.")
222 var utilities={};
223 for (var party in partyprofiles) {
224 utilities[party]=[];
225 }
226 var actions = json['actions'];
227 for (var action in actions) {
228 var offer = actions[action];
229 if (offer['offer']==undefined)
230 continue;
231 var issueutilities = offer['offer']['bid']['issuevalues']
232 for (var party in partyprofiles) {
233 utilities[party].push(utility(partyprofiles[party],issueutilities));
234 }
235 }
236 return utilities;
237
238
239 }
240
241 var chartColors = [
242 'rgb(255, 99, 132)',
243 'rgb(3, 192, 12)',
244 'rgb(54, 162, 235)',
245 'rgb(153, 102, 255)',
246 'rgb(201, 203, 207)',
247 'rgb(255, 159, 64)',
248 'rgb(255, 205, 86)'
249 ];
250
251 function color(i) {
252 return chartColors[i % chartColors.length];
253 }
254
255 /**
256 @param utilities the dict with key=party id of the utilities of all steps for each party )
257 @param isAgreement either Yes, No, Partial or ?
258 */
259
260 function plotResults( utilities, isAgreement) {
261 setStatus("Plotting" )
262
263 document.getElementById('agreement').innerHTML=isAgreement;
264
265 var parties=Object.keys(utilities);
266 var thelabels=[];
267 for (var i=1; i<=utilities[parties[0]].length; i++) { thelabels.push(i); }
268
269 var thedatasets=[];
270 var partynr=0;
271 for (var party in utilities) {
272 var dataset={
273 label: party,
274 lineTension:0, // straight lines between the points
275 backgroundColor: color(partynr),
276 borderColor: color(partynr),
277 data: utilities[party],
278 fill: false
279 };
280 thedatasets.push(dataset);
281 partynr++;
282 }
283
284 var config = {
285 type: 'line',
286 data: {
287 labels: thelabels,
288 datasets: thedatasets
289 },
290 options: {
291 responsive: true,
292 title: {
293 display: true,
294 text: 'Per-turn utility plot'
295 },
296 tooltips: {
297 mode: 'index',
298 intersect: false,
299 },
300 hover: {
301 mode: 'nearest',
302 intersect: true
303 },
304 scales: {
305 xAxes: [{
306 display: true,
307 scaleLabel: {
308 display: true,
309 labelString: 'Offer'
310 }
311 }],
312 yAxes: [{
313 display: true,
314 scaleLabel: {
315 display: true,
316 labelString: 'Utility'
317 }
318 }]
319 }
320 }
321 };
322
323 var ctx = document.getElementById('canvas').getContext('2d');
324 window.myLine = new Chart(ctx, config);
325
326 setStatus("Plot ready." )
327
328 }
329
330 /**
331 keys are STRING objects 0,1,2... values are utility values (double)
332 */
333 function makeJxPolyline(pointslist) {
334 var jxpoints = []
335 for (var i in pointslist) {
336 jxpoints.push(new jxPoint(20*Number(i), 100*pointslist[i]));
337 }
338 return jxpoints;
339 }
340
341 /******************* Private support funcs****************/
342
343 /**
344 Set the progress/status text
345 */
346 function setStatus(text) {
347 document.getElementById('progress').innerHTML=text;
348 }
349
350 /*
351 @param sessions a list of IDs (Strings).
352 */
353 function update(sessions) {
354 var table = document.getElementById("sessionsTable");
355 table.innerHTML="";
356 for(var session of sessions) {
357 var row = table.insertRow(-1); //-1 = end
358 row.insertCell(0).innerHTML = session;
359 }
360
361 }
362
363
364
365
366 /**
367 Compute utilityes
368 @param linadditive the linear additive utility space
369 @param issueValues the bid containing dict with values for the issues
370 @return utility of issueValues
371 */
372 function utility(profile, issueValues) {
373 var util=0;
374 var weights = profile['issueWeights'];
375
376 for (var issue in issueValues) {
377 util = util + weights[issue] * evaluate(profile['issueUtilities'][issue], issueValues[issue] );
378 }
379 return util;
380 }
381
382 /**
383 Bit hacky, javascript version of evaluator of utility space.
384 Would be much nicer if we could use java here
385 */
386 function evaluate (utilfunc, value) {
387 if (utilfunc == undefined) {
388 return 0;
389 }
390 if (utilfunc['numberutils']!=undefined) {
391 // it's numeric issue. Compute
392 var minval = utilfunc['numberutils']['lowValue'];
393 var minutil = utilfunc['numberutils']['lowUtility'];
394 var maxval = utilfunc['numberutils']['highValue'];
395 var maxutil = utilfunc['numberutils']['highUtility'];
396
397 var alfa = (value-minval) / (maxval - minval) ;
398 alfa = Math.max(Math.min(alfa, 1),0);
399 return alfa * maxutil + (1-alfa) * minutil;
400 }
401 if (utilfunc['discreteutils']!=undefined) {
402 // it's discrete issue. Compute
403 return utilfunc['discreteutils']['valueUtilities'][value];
404 }
405 setStatus("Unknown utility function type "+Object.keys(utilfunc));
406
407 }
408
409
410 ]]>
411
412
413
414
415
416
417
418
419
420</script>
421
422
423
424<script type="application/javascript">
425
426
427
428
429
430
431
432
433
434 <![CDATA[
435 "use strict";
436 /*
437 This is called when the window DOM is loaded. window.onload runs BEFORE the window is loaded
438 and therefore is too early to remove the noscript message.
439 */
440 document.addEventListener("DOMContentLoaded", function() {
441 // Remove elements with "noscript" class - <noscript> is not allowed in XHTML
442 var noscripts = document.getElementsByClassName("noscript");
443 for (var i = 0; i < noscripts.length; i++) {
444 noscripts[i].parentNode.removeChild(noscripts[i]);
445 }
446 connect();
447
448 }, false);
449 ]]>
450
451
452
453
454
455
456
457
458
459
460</script>
461
462
463</html>
Note: See TracBrowser for help on using the repository browser.