word_cloud.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. /**
  2. * QUERY SECTION
  3. *
  4. * */
  5. // SPARQL Query text -- 'prefixes' and 'thisUrlParams' defined in people.js;
  6. let queryNetwork = prefixes + (queryManager['querySchedaPersona']['queryNetwork']).replaceAll('{URI}', thisUrlParams.link);
  7. // 'prepareQueryURL' defined in people.js
  8. let queryNet = prepareQueryURL(queryNetwork);
  9. // do the query, call data processor on successful output
  10. $.ajax({
  11. url: queryNet,
  12. dataType: "json",
  13. success: (data) => handle_word_network(data)
  14. });
  15. // queryLetters defined in people.js
  16. /**
  17. * COMPONENT CREATION SECTION
  18. *
  19. * */
  20. // Master function, takes data output from the query and creates the HTML for the Word Cloud component of the page
  21. function handle_word_network(json){
  22. // Pre-process data
  23. let words = processQuery(json);
  24. // Create page elements
  25. doListPersonNetwork(words);
  26. doWordCloud(words);
  27. }
  28. // Return structured object from raw query data for further processing
  29. function processQuery(json) {
  30. const tempArray = [];
  31. $.each(
  32. json['results']['bindings'],
  33. (index, value) => {
  34. let text = value['text']['value'];
  35. let link = value['uri2']['value'];
  36. let sent = parseInt(value['count1']['value']);
  37. let received = parseInt(value['count2']['value']);
  38. let other = parseInt(value['count3']['value']);
  39. let count = sent + received;
  40. tempArray.push({'text': text, 'sent': sent, 'received': received, 'other': other, 'count': count, 'hlink': link});
  41. }
  42. );
  43. tempArray.sort((a, b) => b.count - a.count);
  44. return tempArray;
  45. }
  46. // Create formatted list of people in the network
  47. function doListPersonNetwork(words){
  48. let ArrayNames = "";
  49. words.forEach(element => {
  50. filteredText = element['text'].replace('"', '').replace("'", '');
  51. ArrayNames +=
  52. "<div class='item-place-person'>"+
  53. "<div class='item-place-person-label' id='list-" + filteredText + "'>" +
  54. element['text'] +
  55. "<br /><span onclick='exchangeLetter(\"lettera\", \"" + labelName + "\", \""+ thisUrlParams.link + "\", \""+ filteredText + "\", \""+ element['hlink'] + "\");' class='num_occ'>[Lettere inviate: " + element['sent'] + "]</span>" +
  56. "<br /><span onclick='exchangeLetter(\"lettera\", \"" + "\", \""+ element['hlink'] + "\", \""+ labelName + "\", \""+ thisUrlParams.link + "\");' class='num_occ'>[Lettere ricevute: " + element['received'] + "]</span>" +
  57. "<br /><span class='num_occ'>[Altro: " + element['other'] + "]</span>" +
  58. "<br /><span id='tot-"+filteredText+"' class='num_occ'>[Totale corrispondenza: " + element['count'] + "]</span>" +
  59. "</div>" +
  60. "<div class='item-place-person-action'>" +
  61. "<div class='persona' id='" + element['hlink'] +"'>" +
  62. "<i class='fa fa-user' style='cursor:pointer'></i>" +
  63. "</div>" +
  64. "</div>" +
  65. "</div>";
  66. });
  67. document.getElementById("list_person_network").innerHTML = ArrayNames;
  68. addEventsToNameList(words);
  69. }
  70. function addEventsToNameList(words){
  71. let counter = 0
  72. for(let word of words){
  73. if(counter>0) break;
  74. let listElem = document.getElementById('tot-' + word.text.replace('"', '').replace("'", ''));
  75. if(listElem!=null){
  76. listElem.addEventListener("click", filterLetters);
  77. listElem.addEventListener("mouseover", highlightWord);
  78. listElem.addEventListener("mouseout", unHighlightWord);
  79. }
  80. counter++;
  81. }
  82. }
  83. function filterLetters(e){
  84. try{
  85. let preText = e.target.parentNode.textContent;
  86. console.log('text content', preText);
  87. let name = preText.split("[")[0];
  88. console.log('name', name);
  89. // responseLet defined in people.js
  90. responseLet.then(data => {
  91. let lettersJson = data.results.bindings;
  92. console.log('AH!', lettersJson[0]);
  93. });
  94. } catch{
  95. console.log("Couldn't get the name");
  96. }
  97. }
  98. // Create the word cloud
  99. function doWordCloud(words){
  100. // Clear word cloud div
  101. $('#myWordCloud').empty();
  102. // OVERALL GRAPHIC SETTINGS for the cloud container -- id, dimensions, position
  103. let width = 0.5*window.outerWidth;
  104. let height = 0.6*width;
  105. // append the svg object to the body of the page
  106. let svg = d3.select("#myWordCloud")
  107. .append("svg")
  108. .attr("id", "wordcloudNetwork")
  109. .attr("preserveAspectRatio", "xMinYMin meet")
  110. .attr("viewBox", "0 0 " + width + " " + height)
  111. .classed("svg-content", true)
  112. .attr("width", width)
  113. .attr("height", height)
  114. .attr("style", "border:1px solid black")
  115. // In case of empty cloud, draw special page and exit
  116. if (words.length == 0){
  117. drawEmptyWordCloud();
  118. return;
  119. }
  120. // THE ACTUAL CLOUD
  121. svg = document.getElementById('wordcloudNetwork');
  122. console.log(svg);
  123. let zoom = window.devicePixelRatio;
  124. console.log('zoom', zoom);
  125. wcParameters = {
  126. svgWidth: svg.getBoundingClientRect().width,
  127. svgHeight: svg.getBoundingClientRect().height,
  128. maxFontSize: 30/((1+zoom)/2),
  129. minFontSize: 10/((1+zoom)/2)
  130. }
  131. let svgWords = hackWords(words, wcParameters);
  132. svg.innerHTML = svgWords;
  133. let wordCloudText = createWordCloud(words, wcParameters);
  134. svg.innerHTML = wordCloudText;
  135. addEventsToWordCloud(words);
  136. }
  137. // Helper function -- draw special empty word cloud if there are no occurrences
  138. function drawEmptyWordCloud(){
  139. let wordIcon = "<div id='users_icon' class='no_info_icon'> \
  140. <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' width='30' height='30' viewBox='0 0 256 256' xml:space='preserve'> \
  141. <g transform='translate(128 128) scale(0.72 0.72)'> \
  142. <g style='stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;' transform='translate(-175.05 -175.05000000000004) scale(3.89 3.89)'> \
  143. <path d='M 45 49.519 L 45 49.519 c -7.68 0 -13.964 -6.284 -13.964 -13.964 v -5.008 c 0 -7.68 6.284 -13.964 13.964 -13.964 h 0 c 7.68 0 13.964 6.284 13.964 13.964 v 5.008 C 58.964 43.236 52.68 49.519 45 49.519 z' style='stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;' transform='matrix(1 0 0 1 0 0)' stroke-linecap='round' /> \
  144. <path d='M 52.863 51.438 c -2.362 1.223 -5.032 1.927 -7.863 1.927 s -5.501 -0.704 -7.863 -1.927 C 26.58 53.014 18.414 62.175 18.414 73.152 v 14.444 c 0 1.322 1.082 2.403 2.403 2.403 h 48.364 c 1.322 0 2.403 -1.082 2.403 -2.403 V 73.152 C 71.586 62.175 63.42 53.014 52.863 51.438 z' style='stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;' transform='matrix(1 0 0 1 0 0)' stroke-linecap='round' /> \
  145. <path d='M 71.277 34.854 c -2.362 1.223 -5.032 1.927 -7.863 1.927 c -0.004 0 -0.007 0 -0.011 0 c -0.294 4.412 -2.134 8.401 -4.995 11.43 c 10.355 3.681 17.678 13.649 17.678 24.941 v 0.263 h 11.511 c 1.322 0 2.404 -1.082 2.404 -2.404 V 56.568 C 90 45.59 81.834 36.429 71.277 34.854 z' style='stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;' transform='matrix(1 0 0 1 0 0)' stroke-linecap='round' /> \
  146. <path d='M 63.414 0 c -7.242 0 -13.237 5.589 -13.898 12.667 c 8 2.023 13.947 9.261 13.947 17.881 v 2.385 c 7.657 -0.027 13.914 -6.298 13.914 -13.961 v -5.008 C 77.378 6.284 71.094 0 63.414 0 z' style='stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;' transform='matrix(1 0 0 1 0 0)' stroke-linecap='round' /> \
  147. <path d='M 13.915 73.152 c 0 -11.292 7.322 -21.261 17.677 -24.941 c -2.861 -3.029 -4.702 -7.019 -4.995 -11.43 c -0.004 0 -0.007 0 -0.011 0 c -2.831 0 -5.5 -0.704 -7.863 -1.927 C 8.166 36.429 0 45.59 0 56.568 v 14.444 c 0 1.322 1.082 2.404 2.404 2.404 h 11.511 V 73.152 z' style='stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;' transform='matrix(1 0 0 1 0 0)' stroke-linecap='round' /> \
  148. <path d='M 26.536 32.932 v -2.385 c 0 -8.62 5.946 -15.858 13.947 -17.881 C 39.823 5.589 33.828 0 26.586 0 c -7.68 0 -13.964 6.284 -13.964 13.964 v 5.008 C 12.622 26.635 18.879 32.905 26.536 32.932 z' style='stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;' transform='matrix(1 0 0 1 0 0)' stroke-linecap='round' /> \
  149. </g> \
  150. </g> \
  151. </svg> \
  152. <p>Nessuna persona trovata</p> \
  153. </div>";
  154. $("#person_map").css("display", "none");
  155. $("#wordcloudNetwork").css("display", "none");
  156. $("#references_network").css("display", "none");
  157. document.getElementById("person_map_no_res").innerHTML = wordIcon;
  158. }
  159. /*****************************
  160. * Customized word cloud code *
  161. *****************************/
  162. function hackWords(words, parameters){
  163. let weights = words.map(word => word.count);
  164. let minWeight = Math.min(...weights);
  165. let maxFontSize = parameters.maxFontSize;
  166. let minFontSize = parameters.minFontSize;
  167. console.log('weights', weights);
  168. console.log('maxFontSize', maxFontSize);
  169. console.log('minFontSize',minFontSize);
  170. let stringOfWords = "";
  171. for(let ind=0; ind<words.length; ind++){
  172. let word = words[ind];
  173. word.fontSize = getFontSize(words[ind].count, minWeight, maxFontSize, minFontSize);
  174. stringOfWords = stringOfWords+
  175. '<text x="100" y ="' + (maxFontSize + maxFontSize*ind).toString() + '" + font-family="Verdana" font-size="'+(word.fontSize)+'" id="word' + ind + '">'+words[ind].text+'</text>\n';
  176. }
  177. return stringOfWords;
  178. }
  179. // Takes a list of words, returns the innerHTML for a Word Cloud as a string containing multiple <g> svg elements
  180. function createWordCloud(words, parameters){
  181. let actualUsefulWidth = parameters.svgWidth;
  182. let actualUsefulHeight = parameters.svgHeight;
  183. console.log('Actual SVG dimensions', actualUsefulWidth, actualUsefulHeight);
  184. maxFontSize = parameters.maxFontSize;
  185. minFontSize = parameters.minFontSize;
  186. const rectObjs = []
  187. let allBooms = 0
  188. let attempts = 0;
  189. let outOfBorder = 0;
  190. for(let ind=0;ind<words.length;ind++){
  191. //for(let ind=0;ind<3;ind++){
  192. // Control vars
  193. if(allBooms>=20*(ind+1)) break;
  194. attempts++;
  195. let boom = false;
  196. // word + bounding box parameters definition
  197. let word = words[ind];
  198. let id0 = "word"+ind;
  199. let wordBB = document.getElementById(id0).getBoundingClientRect();
  200. let fontSize = word.fontSize;
  201. let width = wordBB.width;
  202. let height = wordBB.height;
  203. let multiplier = 0.1 + 0.4*Math.max((maxFontSize-fontSize)/(maxFontSize-minFontSize), allBooms/(20*(ind+1)));
  204. let xR = Math.floor(actualUsefulWidth/2-width/2 + randomRange(-multiplier*actualUsefulWidth, multiplier*actualUsefulWidth));
  205. let yR = Math.floor(actualUsefulHeight/2 + randomRange(-multiplier*actualUsefulHeight, multiplier*actualUsefulHeight));
  206. //console.log('x', 'y', xR, yR)
  207. let angle;
  208. if(word.received/word.sent>3) angle = -3;
  209. else if(word.received/word.sent<0.3) angle = 3;
  210. let text = word.text;
  211. //console.log('Rect:', width, height, xR, yR, angle, text);
  212. //
  213. let newRect = new Rect(width, height, xR, yR, angle, text, fontSize);
  214. boom = checkBorder(newRect, 0, actualUsefulWidth, 0, actualUsefulHeight)
  215. if(boom){
  216. outOfBorder++;
  217. if(outOfBorder<30){
  218. ind--;
  219. continue;
  220. } else{
  221. boom = false;
  222. }
  223. }
  224. for(let rect of rectObjs){
  225. boom = checkColl(newRect, rect);
  226. if(boom) break;
  227. }
  228. if(boom){
  229. allBooms++;
  230. ind--;
  231. continue;
  232. }
  233. allBooms = 0;
  234. rectObjs.push(newRect);
  235. }
  236. console.log('Attempted:', attempts);
  237. console.log('Placed:', rectObjs.length);
  238. console.log('booms!', allBooms);
  239. const rectTexts = [];
  240. rectObjs.forEach(rect => rectTexts.push(rect.printCoords(false, maxFontSize, minFontSize)));
  241. let rects = rectTexts.join('\n');
  242. return rects;
  243. }
  244. function getFontSize(weight, minWeight, maxFontSize, minFontSize){
  245. let r1 = weight/10*minWeight - 1/10; // between 0 and a big num
  246. let res = minFontSize + (maxFontSize-minFontSize)*r1/Math.sqrt(1+r1**2);
  247. return res;
  248. }
  249. function randomRange(a, b=0){
  250. return (a-b)*Math.random()+b;
  251. }
  252. function randomInt(a){
  253. return Math.floor(a*Math.random());
  254. }
  255. function randomRangeInt(a, b=0){
  256. let ap = Math.floor(a);
  257. let bp = Math.floor(b);
  258. return Math.floor((ap-bp)*Math.random())+bp;
  259. }
  260. // Check border collision
  261. function checkBorder(rect, minX, maxX, minY, maxY){
  262. if(rect.minX<minX) return true;
  263. if(rect.maxX>maxX) return true;
  264. if(rect.minY<minY) return true;
  265. if(rect.maxY>maxY) return true;
  266. return false;
  267. }
  268. function addEventsToWordCloud(words){
  269. for(let word of words){
  270. let wcElem = document.getElementById('word-' + word.text.replace('"', '').replace("'", ''));
  271. if(wcElem!=null){
  272. wcElem.addEventListener("click", focusPersonInList);
  273. wcElem.addEventListener("mouseover", highlightWord);
  274. wcElem.addEventListener("mouseout", unHighlightWord);
  275. }
  276. }
  277. }
  278. function focusPersonInList(e){
  279. let targetId = e.target.id;
  280. console.log(targetId);
  281. let listElem = document.getElementById(targetId.replace('word-', 'list-'));
  282. listElem.scrollIntoView({behavior: "smooth", block: "center"});
  283. }
  284. function highlightWord(e){
  285. e.target.style['text-decoration'] = "underline";
  286. }
  287. function unHighlightWord(e){
  288. e.target.style['text-decoration'] = "";
  289. }