1 /** Draws the 'weighting Pentagon' and computes the new weighted feature values 2 * Manages also the display of the output structure 3 */ 4 5 /** global variables */ 6 var g_stage; 7 var g_pentagon; 8 var pentaRadius = 70; 9 var g_circle; 10 var g_hexagonLayer; 11 var g_circleLayer; 12 var g_messageLayer; 13 var g_points; 14 var g_choords = [0.2, 0.2, 0.2, 0.2, 0.2]; 15 16 var tmp_num1; 17 var tmp_num2; 18 var tmp_num3; 19 var tmp_num4; 20 var tmp_num5; 21 22 //interpolated using t = 0 ( not readable ) and 1 ( well readable ) 23 var g_value_colors = [{ r: 255, g: 0, b: 0 }, { r: 255, g: 255, b: 255 }, { r: 0, g: 0, b: 255 }]; 24 var g_sentences = [0, 0, 0, 0, 0]; 25 var g_sentence_backup = g_sentences; 26 27 var parameterName = ["word length", "sentence length", "vocabulary complexity", "nominal forms", "sentence structure depth"]; 28 29 /** @class 30 * @param {number} _x x-coordinate 31 */ 32 function point(_x,_y) 33 { 34 this.x = _x; 35 this.y = _y; 36 } 37 38 function testDescriptions() 39 { 40 g_sentences[0] = new SentenceDescription(0.1, 0.5, 0.3, 0.8, 0.4, "Das ist der erste Satz!"); 41 g_sentences[1] = new SentenceDescription(0.6, 0.9, 0.1, 0.2, 0.0, "Das ist der zweite Satz!"); 42 g_sentences[2] = new SentenceDescription(0.1, 0.2, 0.8, 0.3, 0.9, "Das ist der dritte Satz!"); 43 g_sentences[3] = new SentenceDescription(0.5, 0.4, 0.3, 0.6, 0.3, "Das ist der vierte Satz!"); 44 g_sentences[4] = new SentenceDescription(0.3, 0.6, 0.7, 0.4, 0.1, "Das ist der f�nfte Satz!"); 45 } 46 47 function mainPentagon() 48 { 49 //get sentence descriptions 50 if(opener!=null) 51 g_sentences = opener.getDescriptions(); 52 else 53 testDescriptions(); 54 55 g_sentence_backup = g_sentences.slice(); 56 createTables(); 57 fillTable(); 58 updateTables(g_choords); 59 60 g_stage = new Kinetic.Stage({ 61 container: "pentagonContainer", 62 width: 200, 63 height: 180 64 }); 65 66 g_hexagonLayer = new Kinetic.Layer(); 67 g_circleLayer = new Kinetic.Layer(); 68 g_messageLayer = new Kinetic.Layer(); 69 70 g_pentagon = new Kinetic.RegularPolygon({ 71 x: g_stage.getWidth() / 2, 72 y: g_stage.getHeight() / 2, 73 sides :5, 74 radius: pentaRadius, 75 fill: "grey", 76 stroke: "black", 77 alpha: 0.5, 78 strokeWidth: 2 79 }); 80 81 //create reference points 82 g_points = [new point(pentaRadius*Math.cos(18*(Math.PI/180)), pentaRadius*Math.sin(18*(Math.PI/180)))]; 83 for( var i=1; i<6; i++ ) 84 { 85 g_points[i] = new point(pentaRadius*Math.cos((18+72*i)*(Math.PI/180)), 70*Math.sin((18+72*i)*(Math.PI/180))); 86 } 87 88 g_circle= new Kinetic.Circle({ 89 x: g_stage.getWidth() / 2, 90 y: g_stage.getHeight() / 2, 91 radius: 7, 92 fill: "white", 93 stroke: "black", 94 strokeWidth: 2, 95 draggable: true 96 }); 97 98 tmp_num1= new Kinetic.Circle({ 99 x: g_stage.getWidth() / 2 + (pentaRadius + 9)*Math.cos((18)*(Math.PI/180)), 100 y: ( g_stage.getHeight() / 2 ) - (pentaRadius + 9)*Math.sin((18)*(Math.PI/180)), 101 radius: 8, 102 fill: "white", 103 stroke: "grey", 104 strokeWidth: 2 105 }); 106 tmp_num2= new Kinetic.Circle({ 107 x: g_stage.getWidth() / 2 + (pentaRadius + 9)*Math.cos((90)*(Math.PI/180)), 108 y: ( g_stage.getHeight() / 2 ) - (pentaRadius + 9)*Math.sin((90)*(Math.PI/180)), 109 radius: 8, 110 fill: "white", 111 stroke: "grey", 112 strokeWidth: 2 113 }); 114 tmp_num3= new Kinetic.Circle({ 115 x: g_stage.getWidth() / 2 + (pentaRadius + 9)*Math.cos((162)*(Math.PI/180)), 116 y: ( g_stage.getHeight() / 2 ) - (pentaRadius + 9)*Math.sin((162)*(Math.PI/180)), 117 radius: 8, 118 fill: "white", 119 stroke: "grey", 120 strokeWidth: 2 121 }); 122 tmp_num4= new Kinetic.Circle({ 123 x: g_stage.getWidth() / 2 + (pentaRadius + 9)*Math.cos((234)*(Math.PI/180)), 124 y: ( g_stage.getHeight() / 2 ) - (pentaRadius + 9)*Math.sin((234)*(Math.PI/180)), 125 radius: 8, 126 fill: "white", 127 stroke: "grey", 128 strokeWidth: 2 129 }); 130 tmp_num5= new Kinetic.Circle({ 131 x: g_stage.getWidth() / 2 + (pentaRadius + 9)*Math.cos((306)*(Math.PI/180)), 132 y: ( g_stage.getHeight() / 2 ) - (pentaRadius + 9)*Math.sin((306)*(Math.PI/180)), 133 radius: 8, 134 fill: "white", 135 stroke: "grey", 136 strokeWidth: 2 137 }); 138 139 //set callbacks 140 g_circle.on("dragmove", dragCallback); 141 142 // add the shape to the layer 143 g_hexagonLayer.add(g_pentagon); 144 g_hexagonLayer.add(tmp_num1); 145 g_hexagonLayer.add(tmp_num2); 146 g_hexagonLayer.add(tmp_num3); 147 g_hexagonLayer.add(tmp_num4); 148 g_hexagonLayer.add(tmp_num5); 149 g_circleLayer.add(g_circle); 150 151 // add the layer to the stage 152 g_stage.add( g_hexagonLayer ); 153 g_stage.add( g_circleLayer ); 154 g_stage.add( g_messageLayer ); 155 156 writeMessage(g_messageLayer, "1", 157 g_stage.getWidth() / 2 + (pentaRadius + 4)*Math.cos((18-2)*(Math.PI/180)), 158 ( g_stage.getHeight() / 2 ) - (pentaRadius + 4)*Math.sin((18-2)*(Math.PI/180))); 159 writeMessage(g_messageLayer, "2", 160 g_stage.getWidth() / 2 + (pentaRadius + 4)*Math.cos((90+3)*(Math.PI/180)), 161 ( g_stage.getHeight() / 2 ) - (pentaRadius + 4)*Math.sin((90+3)*(Math.PI/180))); 162 writeMessage(g_messageLayer, "3", 163 g_stage.getWidth() / 2 + (pentaRadius + 11)*Math.cos((162+4)*(Math.PI/180)), 164 ( g_stage.getHeight() / 2 ) - (pentaRadius + 11)*Math.sin((162+4)*(Math.PI/180))); 165 writeMessage(g_messageLayer, "4", 166 g_stage.getWidth() / 2 + (pentaRadius + 15)*Math.cos((234 - 1)*(Math.PI/180)), 167 ( g_stage.getHeight() / 2 ) - (pentaRadius + 15)*Math.sin((234 - 1)*(Math.PI/180))); 168 writeMessage(g_messageLayer, "5", 169 g_stage.getWidth() / 2 + (pentaRadius + 10)*Math.cos((306 - 4)*(Math.PI/180)), 170 ( g_stage.getHeight() / 2 ) - (pentaRadius + 10)*Math.sin((306 - 4)*(Math.PI/180))); 171 } 172 173 /** creates the tables for display */ 174 function createTables() 175 { 176 var txttable = document.getElementById("textTable"); 177 var paramtable = document.getElementById("parameterTable"); 178 179 for( var i=0; i<g_sentences.length; i++) 180 { 181 txttable.insertRow(-1); 182 paramtable.insertRow(-1); 183 } 184 } 185 186 /** fills the tables */ 187 function fillTable() 188 { 189 var txtrows = document.getElementById("textTable").rows; 190 191 for( var i=0; i<txtrows.length-1; i++) 192 { 193 var row = txtrows[i+1]; 194 row.innerHTML = "<td>" + g_sentences[i].text + "</td>"; 195 } 196 } 197 /** update the visualization structure */ 198 function updateTables(coords) 199 { 200 adjustHeights(); 201 202 //coords are just the weights 203 204 //TODO iterate over sentences and for every do: 205 var paramrows = document.getElementById("parameterTable").rows; //what actually changes is the color of the texttable 206 for(var i=1; i<paramrows.length; i++) //iterate and get sentence[i-1] values 207 { 208 //set color of text cells 209 var sums = sum(coords); 210 var params = g_sentences[i-1].getProperties(); 211 var t = (coords[0]*params[0]) + 212 + (coords[1]*params[1]) + 213 + (coords[2]*params[2]) + 214 + (coords[3]*params[3]) + 215 + (coords[4]*params[4]); 216 217 var interpolatedColor = calculateColor(t); 218 var txtColString = toHEXColorString(interpolatedColor.r, interpolatedColor.g, interpolatedColor.b); 219 var cell = document.getElementById("textTable").rows[i].cells[0]; 220 cell.bgColor = txtColString; 221 222 var cells = paramrows[i].cells; 223 //set color of parameter cells 224 for(var j=0; j<cells.length; j++) 225 { 226 t = params[j]; 227 interpolatedColor = calculateColor(t); 228 var colString = toHEXColorString(interpolatedColor.r, interpolatedColor.g, interpolatedColor.b); 229 cells[j].bgColor = colString; 230 } 231 } 232 } 233 /** adjust the table row heights */ 234 function adjustHeights() 235 { 236 //TODO adjust row heights this should be done when loading or sorting 237 var txtrows = document.getElementById("textTable").rows; 238 var paramrows = document.getElementById("parameterTable").rows; 239 for(var i=1; i<txtrows.length; i++) 240 { 241 var height = txtrows[i].offsetHeight; 242 var breaks = Math.floor(height/20); 243 var innerHTML = "<td>"; 244 for(var j=0; j<breaks; j++) 245 { 246 innerHTML += "<br/>"; 247 } 248 innerHTML += "</td>"; 249 paramrows[i].innerHTML = innerHTML+innerHTML+innerHTML+innerHTML+innerHTML; 250 } 251 } 252 253 /** sort methods */ 254 function sortOne() 255 { 256 qsort(g_sentences, 0, g_sentences.length, 0); 257 258 fillTable(); 259 updateTables(g_choords); 260 } 261 function sortTwo() 262 { 263 qsort(g_sentences, 0, g_sentences.length, 1); 264 265 fillTable(); 266 updateTables(g_choords); 267 } 268 function sortThree() 269 { 270 qsort(g_sentences, 0, g_sentences.length, 2); 271 272 fillTable(); 273 updateTables(g_choords); 274 } 275 function sortFour() 276 { 277 qsort(g_sentences, 0, g_sentences.length, 3); 278 279 fillTable(); 280 updateTables(g_choords); 281 } 282 function sortFive() 283 { 284 qsort(g_sentences, 0, g_sentences.length, 4); 285 286 fillTable(); 287 updateTables(g_choords); 288 } 289 function sortChrono() 290 { 291 g_sentences = g_sentence_backup.slice(); 292 fillTable(); 293 updateTables(g_choords); 294 } 295 //TODO maybe sweep in another sorting an the overall readability 296 function partition(array, begin, end, pivot, prop) 297 { 298 var piv=array[pivot]; 299 array.swap(pivot, end-1); 300 var store=begin; 301 var ix; 302 for(ix=begin; ix<end-1; ++ix) { 303 if(array[ix].getProperties()[prop]<=piv.getProperties()[prop]) { 304 array.swap(store, ix); 305 ++store; 306 } 307 } 308 array.swap(end-1, store); 309 310 return store; 311 } 312 function qsort(array, begin, end, prop) 313 { 314 if(end-1>begin) { 315 var pivot=begin+Math.floor(Math.random()*(end-begin)); 316 317 pivot=partition(array, begin, end, pivot, prop); 318 319 qsort(array, begin, pivot, prop); 320 qsort(array, pivot+1, end, prop); 321 } 322 } 323 Array.prototype.swap = function(a, b) 324 { 325 var tmp=this[a]; 326 this[a]=this[b]; 327 this[b]=tmp; 328 } 329 330 /** when sphere gets dragged, weights are changed */ 331 function dragCallback(evt) 332 { 333 var circlePos = g_circle.getPosition(); 334 var hexPos = g_pentagon.getPosition(); 335 var x = circlePos.x - hexPos.x; 336 var y = -(circlePos.y - hexPos.y); 337 var pos = {x:x, y:y}; 338 339 //TODO constrain drag area to hexagon 340 //propably use sum of barycentric Coords as soon as they work 341 for(var i = 0; i<5; i++) 342 { 343 if(!isInFront(pos, g_points[i], g_points[i+1])) 344 { 345 //circle dragged outside 346 //stop drag and project onto line 347 var newPos = projectOntoLine(g_points[i], g_points[i+1]); 348 //maybe contraints need to get updated 349 350 alert("outside"); 351 } 352 } 353 354 //update color of circle 355 var pointI = g_points[0]; 356 var pointII = g_points[3]; 357 var t = (x-pointII.x)/(pointI.x-pointII.x); 358 359 var interpolatedColor = calculateColor(t); 360 g_circle.setFill(toRGBColorString(interpolatedColor)); 361 362 //calculate baricentric coords 363 var coords = calculateBaricentric({x:x, y:y}); 364 g_choords = coords; 365 //update tables 366 updateTables(coords); 367 } 368 369 //TODO still wrong - calculate barycentic coordinates 370 function calculateBaricentric(pos) 371 { 372 var max = 0.2485; 373 var min = 0.1725; 374 var coords; 375 376 //calculate maximum possible distance 377 var maxDiff = calcLength(diff(g_points[0], g_points[3])); 378 379 //calculate distances 380 var diffs = [diff(g_points[0], pos), diff(g_points[1], pos), 381 diff(g_points[2], pos), diff(g_points[3], pos), 382 diff(g_points[4], pos)]; 383 coords = diffs; 384 for (var i=0; i<5; i++) 385 { 386 diffs[i] = calcLength(coords[i]); 387 } 388 coords = diffs; 389 390 //relate to total sum 391 var sumofall = sum(coords); 392 for(var i=0; i<5; i++) 393 { 394 coords[i] = sumofall-coords[i]; 395 } 396 sumofall = sum(coords); 397 398 //normalize //not realy correct 399 for(var i=0; i<5; i++) 400 { 401 coords[i] /= sumofall; 402 coords[i] -= min; 403 coords[i] /= max-min; 404 coords[i] = Math.pow(coords[i],3); 405 406 } 407 //normalize so that sum to one 408 sumofall = sum(coords); 409 for(var i=0; i<5; i++) 410 { 411 coords[i] /= sumofall; 412 // console.log(coords[i]+"\n"); 413 } 414 return coords; 415 } 416 417 /** UTIL FUNCTIONS */ 418 419 /** calculate the sum of an array */ 420 function sum(arr) 421 { 422 var sumofall = 0; 423 for(var i=0; i<arr.length; i++) 424 { 425 sumofall += arr[i]; 426 } 427 return sumofall; 428 } 429 /** calculate difference vector */ 430 function diff(vec1, vec2) 431 { 432 return {x: vec2.x-vec1.x, y: vec2.y-vec1.y}; 433 } 434 /** convert to Integer */ 435 function toInt(n){ return Math.round(Number(n)); } 436 /** convert to rgb color string */ 437 function toRGBColorString(rgb){ return "rgb("+rgb.r+","+rgb.g+","+rgb.b+")";} 438 /** calculate length of a vector */ 439 function calcLength(vec){ return Math.sqrt(vec.x*vec.x + vec.y*vec.y);} 440 /** normalize a vector */ 441 function normalize(vec) 442 { 443 var length = calcLength(vec); 444 var nx = vec.x/length; 445 var ny = vec.y/length; 446 var normalizedVector = {x:nx, y:ny}; 447 return normalizedVector; 448 } 449 /** calculate dotproduct */ 450 function dotProduct(v1, v2){ return v1.x*v2.x + v1.y*v2.y; } 451 /**tell if a point is "in front" of a line */ 452 function isInFront(point, vec0, vec1) 453 { 454 var diff = {x:vec1.x - vec0.x, y:vec1.y - vec0.y}; 455 var n = {x: -diff.y, y: diff.x}; 456 var normalizedN = normalize(n); 457 458 var midpoint = midPoint(vec0, vec1); 459 var circleRel = {x: point.x - midpoint.x, y: point.y - midpoint.y}; 460 var normalizedC = normalize(circleRel); 461 462 var dotNC = dotProduct(normalizedN, normalizedC); 463 464 return dotNC>0; 465 } 466 /** caluculate middle point */ 467 function midPoint(vec1, vec2) 468 { 469 var diff = {x:vec2.x - vec1.x,y:vec2.y - vec1.y}; 470 var halfDiff = { x:0.5*diff.x, y:0.5*diff.y }; 471 var midpoint = {x: toInt(vec1.x+halfDiff.x), y: toInt(vec1.y+halfDiff.y)}; 472 473 return midpoint; 474 } 475 /** project a given position onto a line spanned by two points */ 476 function projectOntoLine(pointOne, pointTwo) 477 { 478 var diff = {x:pointTwo.x - pointOne.x, y:pointTwo.y - pointOne.y}; 479 var normalizedN = normalize({x: -diff.y, y: diff.x}); 480 var midpoint = midPoint(pointOne, pointTwo); 481 482 return midpoint; 483 } 484 /** interpolate between to colors */ 485 function calculateColor(t) 486 { 487 var i = 0; 488 var r = 0, g = 0, b = 0; 489 var col1, col2; 490 if(t<0.5) 491 { 492 i = t/0.5; 493 col1 = g_value_colors[0]; 494 col2 = g_value_colors[1]; 495 } 496 else 497 { 498 i = (t - 0.5)/0.5; 499 col1 = g_value_colors[1]; 500 col2 = g_value_colors[2]; 501 } 502 503 r = toInt(i*col2.r + (1-i)*col1.r); 504 g = toInt(i*col2.g + (1-i)*col1.g); 505 b = toInt(i*col2.b + (1-i)*col1.b); 506 var resCol = {r:r, g:g, b:b}; 507 508 return resCol; 509 } 510 /** create HEX color string */ 511 function toHEXColorString(R,G,B) {return toHex(R)+toHex(G)+toHex(B);} 512 function toHex(n) { 513 n = parseInt(n,10); 514 if (isNaN(n)) return "00"; 515 n = Math.max(0,Math.min(n,255)); 516 return "0123456789ABCDEF".charAt((n-n%16)/16) 517 + "0123456789ABCDEF".charAt(n%16); 518 } 519 /** write message to the top left of the stage */ 520 function writeMessage(messageLayer, message, x, y) 521 { 522 var context = messageLayer.getContext(); 523 //messageLayer.clear(); 524 context.font = "12pt Calibri"; 525 context.fillStyle = "black"; 526 context.fillText(message, x, y); 527 } 528