annotate analyse.html @ 429:9bf8ecbcdc8a Dev_main

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