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