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

Last change on this file since 34 was 34, checked in by bart, 3 years ago

Reduced memory need of websockets.

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