Mercurial > hg > webaudioevaluationtool
comparison analyse.html @ 753:66d732c2bc14
Index page now links to example APE project, example MUSHRA project, test creator, analysis page, citing info, GNU license, and instructions. Instructions and example project contain info on checkboxes.
author | Brecht De Man <BrechtDeMan@users.noreply.github.com> |
---|---|
date | Fri, 18 Dec 2015 18:26:46 +0000 |
parents | |
children | babd5366db49 888292c88c33 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 753:66d732c2bc14 |
---|---|
1 <!DOCTYPE html> | |
2 <html lang="en"> | |
3 <head> | |
4 <meta charset="utf-8"> | |
5 | |
6 <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame | |
7 Remove this if you use the .htaccess --> | |
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |
9 | |
10 <title>Analysis</title> | |
11 <meta name="description" content="Show results from subjective evaluation"> | |
12 <meta name="author" content="Brecht De Man"> | |
13 | |
14 <script type="text/javascript" src="https://www.google.com/jsapi"></script> | |
15 <script type="text/javascript"> | |
16 // To aid 'one-page set-up' all scripts and CSS must be included directly in this file! | |
17 | |
18 //google.load("visualization", "1", {packages:["corechart"]}); | |
19 | |
20 /************* | |
21 * SETUP * | |
22 *************/ | |
23 // folder where to find the XML files | |
24 xmlFileFolder = "analysis_test"; | |
25 // array of XML files | |
26 var xmlFiles = ['McG-A-2013-09.xml', 'McG-A-2014-03.xml', 'McG-A-2014-12.xml', 'McG-B-2013-09.xml', | |
27 'McG-B-2014-03.xml', 'McG-B-2014-12.xml', 'McG-C-2013-09.xml', 'McG-C-2014-03.xml', 'McG-C-2014-12.xml', | |
28 'McG-D-2013-09.xml', 'McG-D-2014-03.xml', 'McG-D-2014-12.xml', 'McG-E-2013-09.xml', 'McG-E-2014-03.xml', | |
29 'McG-E-2014-12.xml', 'McG-F-2013-09.xml', 'McG-F-2014-03.xml', 'McG-F-2014-12.xml', 'McG-G-2014-03.xml', | |
30 'McG-G-2014-12.xml', 'McG-H-2013-09.xml', 'McG-H-2014-03.xml', 'McG-H-2014-12.xml', 'McG-I-2013-09.xml', | |
31 'McG-I-2014-03.xml', 'McG-J-2013-09.xml', 'McG-J-2014-03.xml', 'McG-K-2013-09.xml', 'McG-K-2014-03.xml', | |
32 'McG-L-2013-09.xml', 'McG-L-2014-03.xml', 'McG-M-2013-09.xml', 'McG-M-2014-03.xml', 'McG-N-2013-09.xml', | |
33 'McG-N-2014-03.xml', 'McG-O-2013-09.xml', 'McG-O-2014-03.xml', 'McG-P-2013-09.xml', 'McG-P-2014-03.xml', | |
34 'McG-pro1-2013-09.xml', 'McG-pro1-2014-03.xml', 'McG-pro1-2014-12.xml', 'McG-pro2-2013-09.xml', | |
35 'McG-pro2-2014-03.xml', 'McG-pro2-2014-12.xml', 'McG-Q-2014-12.xml', 'McG-R-2014-12.xml', | |
36 'McG-S-2014-12.xml', 'McG-subA-2013-09.xml', 'McG-subA-2014-03.xml', 'McG-subB-2014-03.xml', | |
37 'McG-subB-2014-12.xml', 'McG-subC-2013-09.xml', 'McG-subC-2014-03.xml', 'McG-subC-2014-12.xml', | |
38 'McG-subD-2013-09.xml', 'McG-subD-2014-12.xml', 'McG-subE-2014-12.xml', 'McG-subG-2014-12.xml', | |
39 'McG-subH-2013-09.xml', 'McG-T-2014-12.xml', 'McG-U-2014-12.xml', 'McG-V-2014-12.xml', | |
40 'McG-W-2014-12.xml', 'McG-X-2014-12.xml', 'MG1-2013-09.xml', 'MG2-2013-09.xml', 'MG3-2013-09.xml', | |
41 'MG4-2013-09.xml', 'MG5-2013-09.xml', 'MG6-2013-09.xml', 'MG7-2013-09.xml', 'MG8-2013-09.xml', | |
42 'MG9-2013-09.xml', 'QM-1-1.xml', 'QM-1-2.xml', 'QM-10-1.xml', 'QM-11-1.xml', 'QM-11-2.xml', 'QM-12-1.xml', 'QM-12-2.xml', | |
43 'QM-13-1.xml', 'QM-14-1.xml', 'QM-15-1.xml', 'QM-16-1.xml', 'QM-17-1.xml', 'QM-18-1.xml', 'QM-18-2.xml', | |
44 'QM-18-3.xml', 'QM-19-1.xml', 'QM-2-1.xml', 'QM-2-2.xml', 'QM-2-3.xml', 'QM-20-1.xml', 'QM-20-2.xml', | |
45 'QM-20-3.xml', 'QM-21-1.xml', 'QM-21-2.xml', 'QM-3-1.xml', 'QM-3-2.xml', 'QM-3-3.xml', 'QM-4-1.xml', 'QM-5-1.xml', | |
46 'QM-5-2.xml', 'QM-6-1.xml', 'QM-6-2.xml', 'QM-7-1.xml', 'QM-7-2.xml', 'QM-8-1.xml', 'QM-9-1.xml', | |
47 'PXL-L1.xml','PXL-L2.xml','PXL-L3.xml','PXL-L4.xml','PXL-L5.xml','PXL-S1.xml','PXL-S2.xml','PXL-S3.xml', | |
48 'PXL-S4.xml','PXL-S5.xml','PXL-S6.xml','PXL-S7.xml','PXL-pro.xml','DU-A1.xml','DU-A2.xml','DU-B1.xml', | |
49 'DU-B2.xml','DU-C1.xml','DU-C2.xml','DU-D1.xml','DU-D2.xml','DU-E1.xml','DU-F1.xml','DU-F2.xml','DU-G1.xml', | |
50 'DU-G2.xml','DU-H1.xml','DU-H2.xml','DU-I2.xml','DU-J2.xml','DU-K1.xml','DU-K2.xml','DU-L1.xml','DU-L2.xml', | |
51 'DU-M1.xml','DU-M2.xml','DU-N1.xml','DU-O1.xml','DU-O2.xml','DU-P1.xml','DU-P2.xml','DU-Q1.xml','DU-Q2.xml', | |
52 'DU-R1.xml','DU-R2.xml','DU-S1.xml','DU-S2.xml','DU-T1.xml','DU-T2.xml','DU-U1.xml','DU-U2.xml','DU-U3.xml']; | |
53 //['QM-1-1.xml','QM-2-1.xml','QM-2-2.xml','QM-2-3.xml','QM-3-1.xml','QM-3-2.xml','QM-4-1.xml','QM-5-1.xml','QM-5-2.xml','QM-6-1.xml','QM-6-2.xml','QM-7-1.xml','QM-7-2.xml','QM-8-1.xml','QM-9-1.xml','QM-10-1.xml','QM-11-1.xml','QM-12-1.xml','QM-12-2.xml','QM-13-1.xml','QM-14-1.xml','QM-15-1.xml','QM-16-1.xml','QM-17-1.xml','QM-18-1.xml','QM-18-2.xml','QM-18-3.xml','QM-19-1.xml','QM-20-1.xml','QM-20-2.xml','QM-20-3.xml','QM-21-1.xml','QM-21-2.xml']; | |
54 //['McG-A-2014-03.xml','McG-B-2014-03.xml','McG-C-2014-03.xml','McG-D-2014-03.xml','McG-E-2014-03.xml','McG-F-2014-03.xml','McG-G-2014-03.xml','McG-H-2014-03.xml']; | |
55 | |
56 //TODO: make retrieval of file names automatic / drag files on here | |
57 | |
58 /**************** | |
59 * VARIABLES * | |
60 ****************/ | |
61 | |
62 // Counters | |
63 // How many files, audioholders, audioelementes and statements annotated (don't count current one) | |
64 var numberOfFiles = -1; | |
65 var numberOfaudioholders = -1; | |
66 var numberOfaudioelementes = -1; | |
67 var numberOfStatements = -1; | |
68 var numberOfSkippedComments = 0; | |
69 | |
70 // Object arrays | |
71 var fileNameArray = []; | |
72 var subjectArray = []; | |
73 var audioholderArray = []; | |
74 var audioelementArray = []; | |
75 | |
76 // End of (file, audioholder, audioelement) flags | |
77 var newFile = true; | |
78 var newAudioHolder = true; | |
79 var newAudioElement = true; | |
80 | |
81 var fileCounter = 0; // file index | |
82 var audioholderCounter=0; // audioholder index (current XML file) | |
83 var audioelementCounter=0; // audioelement index (current audioholder) | |
84 var statementNumber=0; // total number of statements | |
85 | |
86 var root; // root of XML file | |
87 var commentInFull = ''; // full comment | |
88 | |
89 var playAudio = true; // whether corresponding audio should be played back | |
90 | |
91 // // Measuring time | |
92 // var lastTimeMeasured = -1; // | |
93 // var durationLastAnnotation = -1; // duration of last annotation | |
94 // var timeArray = []; | |
95 // var MIN_TIME = 1.0; // minimum time counted as significant | |
96 // var measurementPaused = false; // whether time measurement is paused | |
97 // var timeInBuffer = 0; // | |
98 | |
99 var topLevel; | |
100 window.onload = function() { | |
101 // Initialise page | |
102 topLevel = document.getElementById('topLevelBody'); | |
103 var setup = document.createElement('div'); | |
104 setup.id = 'setupTagDiv'; | |
105 loadAllFiles(); | |
106 printSurveyData() | |
107 //makePlots(); | |
108 // measure time at this point: | |
109 lastTimeMeasured = new Date().getTime(); // in milliseconds | |
110 }; | |
111 | |
112 // Assert function | |
113 function assert(condition, message) { | |
114 if (!condition) { | |
115 message = message || "Assertion failed"; | |
116 if (typeof Error !== "undefined") { | |
117 throw new Error(message); | |
118 } | |
119 throw message; // Fallback | |
120 } | |
121 } | |
122 | |
123 function median(values) { // TODO: replace code by '50th percentile' - should be the same? | |
124 values.sort( function(a,b) {return a - b;} ); | |
125 var half = Math.floor(values.length/2); | |
126 if(values.length % 2) | |
127 return values[half]; | |
128 else | |
129 return (values[half-1] + values[half]) / 2.0; | |
130 } | |
131 | |
132 function percentile(values, n) { | |
133 values.sort( function(a,b) {return a - b;} ); | |
134 // get ordinal rank | |
135 var rank = Math.min(Math.floor(values.length*n/100), values.length-1); | |
136 return values[rank]; | |
137 } | |
138 | |
139 /*********************** | |
140 * TIME MEASUREMENT * | |
141 ************************/ | |
142 | |
143 // measure time since last time this function was called | |
144 function timeSinceLastCall() { | |
145 // current time | |
146 var currentTime = new Date().getTime(); | |
147 // calculate time difference | |
148 var timeDifference = currentTime - lastTimeMeasured + timeInBuffer; | |
149 // clear buffer (for pausing) | |
150 timeInBuffer = 0; | |
151 // remember last measured time | |
152 lastTimeMeasured = currentTime; | |
153 return timeDifference; | |
154 } | |
155 | |
156 // pause time measurement | |
157 function pauseTimeMeasurement() { | |
158 // UN-PAUSE | |
159 if (measurementPaused) { // already paused | |
160 // button shows 'pause' again | |
161 document.getElementById('pauseButton').innerHTML = 'Pause'; | |
162 // toggle state | |
163 measurementPaused = false; | |
164 // resume time measurement | |
165 lastTimeMeasured = new Date().getTime(); // reset time, discard time while paused | |
166 } else { // PAUSE | |
167 // button shows 'resume' | |
168 document.getElementById('pauseButton').innerHTML = 'Resume'; | |
169 // toggle state | |
170 measurementPaused = true; | |
171 // pause time measurement | |
172 timeInBuffer = timeSinceLastCall(); | |
173 } | |
174 } | |
175 | |
176 // show elapsed time on interface | |
177 function showTimeElapsedInSeconds() { | |
178 // if paused: un-pause | |
179 if (measurementPaused) { | |
180 pauseTimeMeasurement(); | |
181 } | |
182 | |
183 // time of last annotation | |
184 var lastAnnotationTime = timeSinceLastCall()/1000; | |
185 document.getElementById('timeDisplay').innerHTML = lastAnnotationTime.toFixed(2); | |
186 // average time over last ... annotations | |
187 var avgAnnotationTime; | |
188 var numberOfElementsToAverage = | |
189 document.getElementById('numberOfTimeAverages').value; | |
190 if (isPositiveInteger(numberOfElementsToAverage)) { | |
191 avgAnnotationTime = | |
192 calculateAverageTime(lastAnnotationTime, | |
193 Number(numberOfElementsToAverage)); | |
194 } else { | |
195 // change text field content to 'ALL' | |
196 document.getElementById('numberOfTimeAverages').value = 'ALL'; | |
197 avgAnnotationTime = calculateAverageTime(lastAnnotationTime, -1); | |
198 } | |
199 document.getElementById('timeAverageDisplay').innerHTML = avgAnnotationTime.toFixed(2); | |
200 } | |
201 | |
202 // auxiliary function: is string a positive integer? | |
203 // http://stackoverflow.com/questions/10834796/... | |
204 // validate-that-a-string-is-a-positive-integer | |
205 function isPositiveInteger(str) { | |
206 var n = ~~Number(str); | |
207 return String(n) === str && n >= 0; | |
208 } | |
209 | |
210 // calculate average time | |
211 function calculateAverageTime(newTimeMeasurementInSeconds,numberOfPoints) { | |
212 // append last measurement time to time array, if significant | |
213 if (newTimeMeasurementInSeconds > MIN_TIME) { | |
214 timeArray.push(newTimeMeasurementInSeconds); | |
215 } | |
216 // average over last N elements of this array | |
217 if (numberOfPoints < 0 || numberOfPoints>=timeArray.length) { // calculate average over all | |
218 var sum = 0; | |
219 for (var i = 0; i < timeArray.length; i++) { | |
220 sum += timeArray[i]; | |
221 } | |
222 averageOfTimes = sum/timeArray.length; | |
223 } else { // calculate average over specified number of times measured last | |
224 var sum = 0; | |
225 for (var i = timeArray.length-numberOfPoints; i < timeArray.length; i++) { | |
226 sum += timeArray[i]; | |
227 } | |
228 averageOfTimes = sum/numberOfPoints; | |
229 } | |
230 return averageOfTimes; | |
231 } | |
232 | |
233 | |
234 /******************************** | |
235 * PLAYBACK OF AUDIO * | |
236 ********************************/ | |
237 | |
238 //PLAYaudioelement | |
239 // Keep track of whether audio should be played | |
240 function playFlagChanged(){ | |
241 playAudio = playFlag.checked; // global variable | |
242 | |
243 if (!playAudio){ // if audio needs to stop | |
244 audio.pause(); // stop audio - if anything is playing | |
245 currently_playing = ''; // back to empty string so playaudioelement knows nothing's playing | |
246 } | |
247 } | |
248 | |
249 // audioholder that's currently playing | |
250 var currently_playing_audioholder = ''; // at first: empty string | |
251 var currently_playing_audioelement = ''; | |
252 var audio; | |
253 | |
254 // Play audioelement of audioholder if available, from start or from same position | |
255 function playaudioelement(audioholderName, audioelementerName){ | |
256 if (playAudio) { // if enabled | |
257 // get corresponding file from folder | |
258 var file_location = 'audio/'+audioholderName + '/' + audioelementerName + '.mp3'; // fixed path and file name format | |
259 | |
260 // if not available, show error/warning message | |
261 //TODO ... | |
262 | |
263 // if nothing playing yet, start playing | |
264 if (currently_playing_audioholder == ''){ // signal that nothing is playing | |
265 //playSound(audioBuffer); | |
266 audio = new Audio(file_location); | |
267 audio.loop = true; // loop when end is reached | |
268 audio.play(); | |
269 currently_playing_audioholder = audioholderName; | |
270 currently_playing_audioelement = audioelementerName; | |
271 } else if (currently_playing_audioholder != audioholderName) { | |
272 // if different audioholder playing, stop that and start playing | |
273 audio.pause(); // stop audio | |
274 audio = new Audio(file_location); // load new file | |
275 audio.loop = true; // loop when end is reached | |
276 audio.play(); // play audio from the start | |
277 currently_playing_audioholder = audioholderName; | |
278 currently_playing_audioelement = audioelementerName; | |
279 } else if (currently_playing_audioelement != audioelementerName) { | |
280 // if same audioholder playing, start playing from where it left off | |
281 skipTime = audio.currentTime; // time to skip to | |
282 audio.pause(); // stop audio | |
283 audio = new Audio(file_location); | |
284 audio.addEventListener('loadedmetadata', function() { | |
285 this.currentTime = skipTime; | |
286 console.log('Loaded '+audioholderName+'-'+audioelementerName+', playing from '+skipTime); | |
287 }, false); // skip to same time when audio is loaded! | |
288 audio.loop = true; // loop when end is reached | |
289 audio.play(); // play from that time | |
290 audio.currentTime = skipTime; | |
291 currently_playing_audioholder = audioholderName; | |
292 currently_playing_audioelement = audioelementerName; | |
293 } | |
294 // if same audioelement playing: keep on playing (i.e. do nothing) | |
295 } | |
296 } | |
297 | |
298 /******************** | |
299 * READING FILES * | |
300 ********************/ | |
301 | |
302 // Read necessary data from XML file | |
303 function readXML(xmlFileName){ | |
304 if (window.XMLHttpRequest) | |
305 {// code for IE7+, Firefox, Chrome, Opera, Safari | |
306 xmlhttp=new XMLHttpRequest(); | |
307 } | |
308 else | |
309 {// code for IE6, IE5 | |
310 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); | |
311 } | |
312 xmlhttp.open("GET",xmlFileName,false); | |
313 xmlhttp.send(); | |
314 return xmlhttp.responseXML; | |
315 } | |
316 | |
317 // go over all files and compute relevant statistics | |
318 function loadAllFiles() { | |
319 // retrieve information from XMLs | |
320 | |
321 for (fileIndex = 0; fileIndex < xmlFiles.length; fileIndex++) { | |
322 xmlFileName = xmlFileFolder+"/"+xmlFiles[fileIndex]; | |
323 xml = readXML(xmlFileName); | |
324 if (xml != null) { // if file exists | |
325 // append file name to array of file names | |
326 fileNameArray.push(xmlFiles[fileIndex]); | |
327 | |
328 // get root of XML file | |
329 root = xml.getElementsByTagName('browserevaluationresult')[0]; | |
330 | |
331 // get subject ID, add to array if not already there | |
332 pretest = root.getElementsByTagName('pretest')[0]; | |
333 subjectID = pretest.getElementsByTagName('comment')[0]; | |
334 if (subjectID){ | |
335 if (subjectID.getAttribute('id')!='sessionId') { // warning in console when not available | |
336 console.log(xmlFiles[fileIndex]+': no SessionID available'); | |
337 } | |
338 if (subjectArray.indexOf(subjectID.textContent) == -1) { // if not already in array | |
339 subjectArray.push(subjectID.textContent); // append to array | |
340 } | |
341 } | |
342 | |
343 // go over all audioholders, add to array if not already there | |
344 audioholderNodes = root.getElementsByTagName('audioholder'); | |
345 // go over audioholderNodes and append audioholder name when not present yet | |
346 for (audioholderIndex = 0; audioholderIndex < audioholderNodes.length; audioholderIndex++) { | |
347 audioholderName = audioholderNodes[audioholderIndex].getAttribute('id'); | |
348 if (audioholderArray.indexOf(audioholderName) == -1) { // if not already in array | |
349 audioholderArray.push(audioholderName); // append to array | |
350 } | |
351 // within each audioholder, go over all audioelement IDs, add to array if not already there | |
352 audioelementNodes = audioholderNodes[audioholderIndex].getElementsByTagName('audioelement'); | |
353 for (audioelementIndex = 0; audioelementIndex < audioelementNodes.length; audioelementIndex++) { | |
354 audioelementName = audioelementNodes[audioelementIndex].getAttribute('id'); | |
355 if (audioelementArray.indexOf(audioelementName) == -1) { // if not already in array | |
356 audioelementArray.push(audioelementName); // append to array | |
357 } | |
358 } | |
359 } | |
360 // count occurrences of each audioholder | |
361 // ... | |
362 } | |
363 else { | |
364 console.log('XML file '+xmlFileName+' not found.'); | |
365 } | |
366 } | |
367 | |
368 // sort alphabetically | |
369 fileNameArray.sort(); | |
370 subjectArray.sort(); | |
371 audioholderArray.sort(); | |
372 audioelementArray.sort(); | |
373 | |
374 // display all information in HTML | |
375 // show XML file folder | |
376 document.getElementById('xmlFileFolder_span').innerHTML = "\""+xmlFileFolder+"/\""; | |
377 // show number of files | |
378 document.getElementById('numberOfFiles_span').innerHTML = fileNameArray.length; | |
379 // show list of subject names | |
380 document.getElementById('subjectArray_span').innerHTML = subjectArray.toString(); | |
381 // show list of audioholders | |
382 document.getElementById('audioholderArray_span').innerHTML = audioholderArray.toString(); | |
383 // show list of audioelementes | |
384 document.getElementById('audioelementArray_span').innerHTML = audioelementArray.toString(); | |
385 } | |
386 | |
387 function printSurveyData() { | |
388 // print some fields from the survey for different people | |
389 | |
390 // go over all XML files | |
391 for (fileIndex = 0; fileIndex < xmlFiles.length; fileIndex++) { | |
392 xmlFileName = xmlFileFolder+"/"+xmlFiles[fileIndex]; | |
393 xml = readXML(xmlFileName); | |
394 // make a div | |
395 var div = document.createElement('div'); | |
396 document.body.appendChild(div); | |
397 div.id = 'div_survey_'+xmlFileName; | |
398 div.style.width = '1100px'; | |
399 //div.style.height = '350px'; | |
400 | |
401 // title for that div (subject id) | |
402 document.getElementById('div_survey_'+xmlFileName).innerHTML = '<h2>'+xmlFileName+'</h2>'; | |
403 | |
404 // which songs did they do | |
405 if (xml != null) { // if file exists | |
406 // get root of XML file | |
407 root = xml.getElementsByTagName('browserevaluationresult')[0]; | |
408 // go over all audioholders | |
409 // document.getElementById('div_survey_'+xmlFileName).innerHTML += '<strong>Audioholders: </strong>'; | |
410 // audioholderNodes = root.getElementsByTagName('audioholder'); | |
411 // for (audioholderIndex = 0; audioholderIndex < audioholderNodes.length-1; audioholderIndex++) { | |
412 // document.getElementById('div_survey_'+xmlFileName).innerHTML += audioholderNodes[audioholderIndex].getAttribute('id')+', '; | |
413 // } | |
414 // document.getElementById('div_survey_'+xmlFileName).innerHTML += audioholderNodes[audioholderNodes.length-1].getAttribute('id'); | |
415 | |
416 // survey responses (each if available) | |
417 // get posttest node for total test | |
418 childNodes = root.childNodes; | |
419 posttestnode = null; | |
420 for (idx = 0; idx < childNodes.length; idx++){ | |
421 if (childNodes[childNodes.length-idx-1].tagName == 'posttest') { | |
422 posttestnode = childNodes[childNodes.length-idx-1]; | |
423 break; | |
424 } | |
425 } | |
426 | |
427 // mix experience | |
428 if (posttestnode) { | |
429 posttestcomments = posttestnode.getElementsByTagName('comment'); | |
430 for (idx=0; idx < posttestcomments.length; idx++){ | |
431 commentsToPrint = ['generalExperience', 'interfaceExperience']; | |
432 idAttribute = posttestcomments[idx].getAttribute('id'); | |
433 if (commentsToPrint.indexOf(idAttribute) >= 0) { // if exists? | |
434 document.getElementById('div_survey_'+xmlFileName).innerHTML += '<br><strong>'+idAttribute+': </strong>'+posttestcomments[idx].textContent; | |
435 } | |
436 } | |
437 } | |
438 } | |
439 } | |
440 } | |
441 | |
442 function makePlots() { //TODO: split into different functions | |
443 // TEMPORARY | |
444 makeTimeline(xmlFileFolder+"/"+xmlFiles[7]); | |
445 | |
446 // create value array | |
447 var ratings = []; // 3D matrix of ratings (audioholder, audioelement, subject) | |
448 for (audioholderIndex = 0; audioholderIndex < audioholderArray.length; audioholderIndex++) { | |
449 ratings.push([]); | |
450 for (audioelementIndex = 0; audioelementIndex < audioelementArray.length; audioelementIndex++) { | |
451 ratings[audioholderIndex].push([]); | |
452 } | |
453 } | |
454 | |
455 // go over all XML files | |
456 for (fileIndex = 0; fileIndex < xmlFiles.length; fileIndex++) { | |
457 xmlFileName = xmlFileFolder+"/"+xmlFiles[fileIndex]; | |
458 xml = readXML(xmlFileName); | |
459 if (xml != null) { // if file exists | |
460 // get root of XML file | |
461 root = xml.getElementsByTagName('browserevaluationresult')[0]; | |
462 // go over all audioholders | |
463 audioholderNodes = root.getElementsByTagName('audioholder'); | |
464 for (audioholderIndex = 0; audioholderIndex < audioholderNodes.length; audioholderIndex++) { | |
465 audioholderName = audioholderNodes[audioholderIndex].getAttribute('id'); | |
466 audioelementNodes = audioholderNodes[audioholderIndex].getElementsByTagName('audioelement'); | |
467 // go over all audioelements | |
468 for (audioelementIndex = 0; audioelementIndex < audioelementNodes.length; audioelementIndex++) { | |
469 audioelementName = audioelementNodes[audioelementIndex].getAttribute('id'); | |
470 // get value | |
471 var value = audioelementNodes[audioelementIndex].getElementsByTagName("value")[0].textContent; | |
472 if (value) { // if not empty, null, undefined... | |
473 ratingValue = parseFloat(value); | |
474 // add to matrix at proper position | |
475 aHidx = audioholderArray.indexOf(audioholderName); | |
476 aEidx = audioelementArray.indexOf(audioelementName); | |
477 ratings[aHidx][aEidx].push(ratingValue); | |
478 } | |
479 } | |
480 } | |
481 | |
482 // go over all audioholders | |
483 | |
484 // go over all audioelements within audioholder, see if present in idMatrix, add if not | |
485 // add corresponding rating to 'ratings', at position corresponding with position in idMatrix | |
486 } | |
487 } | |
488 | |
489 for (audioholderIndex = 0; audioholderIndex < audioholderArray.length; audioholderIndex++) { | |
490 audioholderName = audioholderArray[audioholderIndex]; // for this song | |
491 tickArray = [] | |
492 | |
493 raw_data = [['SubjectID', 'Rating']]; | |
494 audioElIdx = 0; | |
495 for (audioelementIndex = 0; audioelementIndex<ratings[audioholderIndex].length; audioelementIndex++){ | |
496 if (ratings[audioholderIndex][audioelementIndex].length>0) { | |
497 audioElIdx++; // increase if not empty | |
498 // make tick label | |
499 tickArray.push({v:audioElIdx, f: audioelementArray[audioelementIndex]}); | |
500 } | |
501 for (subject = 0; subject<ratings[audioholderIndex][audioelementIndex].length; subject++){ | |
502 // add subject-value pair for each subject | |
503 raw_data.push([audioElIdx, ratings[audioholderIndex][audioelementIndex][subject]]); | |
504 } | |
505 } | |
506 | |
507 // create plot (one per song) | |
508 var data = google.visualization.arrayToDataTable(raw_data); | |
509 | |
510 var options = { | |
511 title: audioholderName, | |
512 hAxis: {title: 'audioelement ID', minValue: 0, maxValue: audioElIdx+1, | |
513 ticks: tickArray}, | |
514 vAxis: {title: 'Rating', minValue: 0, maxValue: 1}, | |
515 seriesType: 'scatter', | |
516 legend: 'none' | |
517 }; | |
518 var div = document.createElement('div'); | |
519 document.body.appendChild(div); | |
520 div.id = 'div_'+audioholderName; | |
521 div.style.width = '1100px'; | |
522 div.style.height = '350px'; | |
523 var chart = new google.visualization.ComboChart(document.getElementById('div_'+audioholderName)); | |
524 chart.draw(data, options); | |
525 | |
526 // box plots | |
527 var div = document.createElement('div'); | |
528 document.body.appendChild(div); | |
529 div.id = 'div_box_'+audioholderName; | |
530 div.style.width = '1100px'; | |
531 div.style.height = '350px'; | |
532 // Get median, percentiles, maximum and minimum; outliers. | |
533 pctl25 = []; | |
534 pctl75 = []; | |
535 med = []; | |
536 min = []; | |
537 max = []; | |
538 outlierArray = []; | |
539 max_n_outliers = 0; // maximum number of outliers for one audioelement | |
540 for (audioelementIndex = 0; audioelementIndex<ratings[audioholderIndex].length; audioelementIndex++){ | |
541 med.push(median(ratings[audioholderIndex][audioelementIndex])); // median | |
542 pctl25.push(percentile(ratings[audioholderIndex][audioelementIndex], 25)); // 25th percentile | |
543 pctl75.push(percentile(ratings[audioholderIndex][audioelementIndex], 75)); // 75th percentile | |
544 IQR = pctl75[pctl75.length-1]-pctl25[pctl25.length-1]; | |
545 // outliers: range of values which is above pctl75+1.5*IQR or below pctl25-1.5*IQR | |
546 outliers = []; | |
547 rest = []; | |
548 for (idx = 0; idx<ratings[audioholderIndex][audioelementIndex].length; idx++){ | |
549 if (ratings[audioholderIndex][audioelementIndex][idx] > pctl75[pctl75.length-1]+1.5*IQR || | |
550 ratings[audioholderIndex][audioelementIndex][idx] < pctl25[pctl25.length-1]-1.5*IQR){ | |
551 outliers.push(ratings[audioholderIndex][audioelementIndex][idx]); | |
552 } | |
553 else { | |
554 rest.push(ratings[audioholderIndex][audioelementIndex][idx]); | |
555 } | |
556 } | |
557 outlierArray.push(outliers); | |
558 max_n_outliers = Math.max(max_n_outliers, outliers.length); // update max mber | |
559 // max: maximum value which is not outlier | |
560 max.push(Math.max.apply(null, rest)); | |
561 // min: minimum value which is not outlier | |
562 min.push(Math.min.apply(null, rest)); | |
563 } | |
564 | |
565 // Build data array | |
566 boxplot_data = [['ID', 'Span', '', '', '', 'Median']]; | |
567 for (idx = 0; idx < max_n_outliers; idx++) { | |
568 boxplot_data[0].push('Outlier'); | |
569 } | |
570 for (audioelementIndex = 0; audioelementIndex<ratings[audioholderIndex].length; audioelementIndex++){ | |
571 if (ratings[audioholderIndex][audioelementIndex].length>0) { // if rating array not empty for this audioelement | |
572 data_array = [ | |
573 audioelementArray[audioelementIndex], // name | |
574 min[audioelementIndex], // minimum | |
575 pctl75[audioelementIndex], | |
576 pctl25[audioelementIndex], | |
577 max[audioelementIndex], // maximum | |
578 med[audioelementIndex] | |
579 ]; | |
580 for (idx = 0; idx < max_n_outliers; idx++) { | |
581 if (idx<outlierArray[audioelementIndex].length){ | |
582 data_array.push(outlierArray[audioelementIndex][idx]); | |
583 } | |
584 else { | |
585 data_array.push(null); | |
586 } | |
587 } | |
588 boxplot_data.push(data_array); | |
589 } | |
590 } | |
591 | |
592 // Create and populate the data table. | |
593 var data = google.visualization.arrayToDataTable(boxplot_data); | |
594 // Create and draw the visualization. | |
595 var ac = new google.visualization.ComboChart(document.getElementById('div_box_'+audioholderName)); | |
596 ac.draw(data, { | |
597 title : audioholderName, | |
598 //width: 600, | |
599 //height: 400, | |
600 vAxis: {title: "Rating"}, | |
601 hAxis: {title: "audioelement ID"}, | |
602 seriesType: "line", | |
603 pointSize: 5, | |
604 lineWidth: 0, | |
605 colors: ['black'], | |
606 series: { 0: {type: "candlesticks", color: 'blue'}, // box plot shape | |
607 1: {type: "line", pointSize: 10, lineWidth: 0, color: 'red' } }, // median | |
608 legend: 'none' | |
609 }); | |
610 } | |
611 } | |
612 | |
613 function makeTimeline(xmlFileName){ // WIP | |
614 // Based on the XML file name, take time data and plot playback and marker movements | |
615 | |
616 // read XML file and check if exists | |
617 xml = readXML(xmlFileName); | |
618 if (!xml) { // if file does not exist | |
619 console.log('XML file '+xml+'does not exist. ('+xmlFileName+')') | |
620 return; // do nothing; exit function | |
621 } | |
622 // get root of XML file | |
623 root = xml.getElementsByTagName('browserevaluationresult')[0]; | |
624 | |
625 audioholder_time = 0; | |
626 previous_audioholder_time = 0; // time spent before current audioholder | |
627 time_offset = 0; // test starts at zero | |
628 | |
629 // go over all audioholders | |
630 audioholderNodes = root.getElementsByTagName('audioholder'); | |
631 for (audioholderIndex = 0; audioholderIndex < audioholderNodes.length; audioholderIndex++) { | |
632 audioholderName = audioholderNodes[audioholderIndex].getAttribute('id'); | |
633 if (!audioholderName) { | |
634 console.log('audioholder name is empty; go to next one. ('+xmlFileName+')'); | |
635 break; | |
636 } | |
637 | |
638 // subtract total audioholder length from subsequent audioholder event times | |
639 audioholder_children = audioholderNodes[audioholderIndex].childNodes; | |
640 foundIt = false; | |
641 console.log(audioholder_children[2].getElementsByTagName("metricResult")) // not working! | |
642 for (idx = 0; idx<audioholder_children.length; idx++) { // go over children | |
643 | |
644 if (audioholder_children[idx].getElementsByTagName('metricResult').length) { | |
645 console.log(audioholder_children[idx].getElementsByTagName('metricResult')[0]); | |
646 if (audioholder_children[idx].getElementsByTagName('metricResult')[0].getAttribute('id') == "testTime"){ | |
647 audioholder_time = parseFloat(audioholder_children[idx].getElementsByTagName('metricResult')[0].textContent); | |
648 console.log(audioholder_time); | |
649 foundIt = true; | |
650 } | |
651 } | |
652 } | |
653 if (!foundIt) { | |
654 console.log("Skipping audioholder without total time specified from "+xmlFileName+"."); // always hitting this | |
655 break; | |
656 } | |
657 | |
658 audioelementNodes = audioholderNodes[audioholderIndex].getElementsByTagName('audioelement'); | |
659 | |
660 // make div | |
661 | |
662 // draw chart | |
663 | |
664 // legend with audioelement names | |
665 } | |
666 } | |
667 | |
668 </script> | |
669 | |
670 | |
671 | |
672 <style> | |
673 div { | |
674 padding: 2px; | |
675 margin-top: 2px; | |
676 margin-bottom: 2px; | |
677 } | |
678 div.head{ | |
679 margin-left: 10px; | |
680 border: black; | |
681 border-width: 2px; | |
682 border-style: solid; | |
683 } | |
684 div.attrib{ | |
685 margin-left:25px; | |
686 border: black; | |
687 border-width: 2px; | |
688 border-style: dashed; | |
689 margin-bottom: 10px; | |
690 } | |
691 div#headerMatter{ | |
692 background-color: #FFFFCC; | |
693 } | |
694 div#currentStatement{ | |
695 font-size:3.0em; | |
696 font-weight: bold; | |
697 | |
698 } | |
699 div#debugDisplay { | |
700 color: #CCCCCC; | |
701 font-size:0.3em; | |
702 } | |
703 span#scoreDisplay { | |
704 font-weight: bold; | |
705 } | |
706 div#wrapper { | |
707 width: 780px; | |
708 border: 1px solid black; | |
709 overflow: hidden; /* add this to contain floated children */ | |
710 } | |
711 div#instrumentSection { | |
712 width: 250px; | |
713 border: 1px solid red; | |
714 display: inline-block; | |
715 } | |
716 div#featureSection { | |
717 width: 250px; | |
718 border: 1px solid green; | |
719 display: inline-block; | |
720 } | |
721 div#valenceSection { | |
722 width: 250px; | |
723 border: 1px solid blue; | |
724 display: inline-block; | |
725 } | |
726 button#previousComment{ | |
727 width: 120px; | |
728 height: 150px; | |
729 font-size:1.5em; | |
730 } | |
731 button#nextComment{ | |
732 width: 666px; | |
733 height: 150px; | |
734 font-size:1.5em; | |
735 } | |
736 ul | |
737 { | |
738 list-style-type: none; /* no bullet points */ | |
739 margin-left: -20px; /* less indent */ | |
740 margin-top: 0px; | |
741 margin-bottom: 5px; | |
742 } | |
743 </style> | |
744 | |
745 </head> | |
746 | |
747 <body> | |
748 <h1>Subjective evaluation results</h1> | |
749 | |
750 <div id="debugDisplay"> | |
751 XML file folder: <span id="xmlFileFolder_span"></span> | |
752 </div> | |
753 | |
754 <div id="headerMatter"> | |
755 <div> | |
756 <strong>Result XML files:</strong> <span id="numberOfFiles_span"></span> | |
757 </div> | |
758 <div> | |
759 <strong>Audioholders in dataset:</strong> <span id="audioholderArray_span"></span> | |
760 </div> | |
761 <div> | |
762 <strong>Subjects in dataset:</strong> <span id="subjectArray_span"></span> | |
763 </div> | |
764 <div> | |
765 <strong>Audioelements in dataset:</strong> <span id="audioelementArray_span"></span> | |
766 </div> | |
767 <br> | |
768 </div> | |
769 <br> | |
770 | |
771 <!-- Show time elapsed | |
772 The last annotation took <strong><span id="timeDisplay">(N/A)</span></strong> seconds. | |
773 <br>--> | |
774 | |
775 </body> | |
776 </html> |