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>