Source: Metanodes.js

  1. var nodesOld = []; //contains the original graph nodes
  2. var linksOld = []; //contains the original graph links
  3. var nodes_communitys; //contains all nodes with its community
  4. var amountMetaN; //contains the overall number of communities
  5. //contains the new meta-nodes
  6. var newMetaNodes = [];
  7. //contains the new meta-links
  8. var newLinks = [];
  9. var linksD = []; //contains the meta-links in a drawable form
  10. var nodesD = []; //contains the meta-nodes in a drawable form
  11. var dataset = []; //contains data for statistic
  12. /**
  13. * This function creates metanodes out of nodes according to their community attribute.
  14. * This is done by creating for every community a node and the size of the node is determined by the number of associated
  15. * nodes. The strength of the links between these new nodes, which represent the metanodes, is determined by the number
  16. * of links between the original nodes. The more connections between a pair of communities exists, the bigger the link is
  17. * drawn.
  18. * @param {Array} nodesV contains all nodes of the original graph
  19. * @param {Array} linksV contains all links of the original graph
  20. * @param {Array} result contains the id's of all nodes with its community number
  21. * @param {number} communityCount contains the overall number of communities
  22. */
  23. function createMetanodes(nodesV,linksV,result,communityCount)
  24. {
  25. //initalize empty
  26. nodesOld = []; //contains the original graph nodes
  27. linksOld = []; //contains the original graph links
  28. nodes_communitys; //contains all nodes with its community
  29. amountMetaN; //contains the overall number of communities
  30. newMetaNodes = [];
  31. newLinks = [];
  32. linksD = []; //contains the meta-links in a drawable form
  33. nodesD = []; //contains the meta-nodes in a drawable form
  34. dataset = []; //contains data for statistic
  35. nodesOld = nodesV;
  36. linksOld = linksV;
  37. nodes_communitys = result;
  38. //need as many nodes as communities
  39. amountMetaN = communityCount;
  40. //for every community create metanode
  41. for(var i = 0; i < amountMetaN; i++)
  42. {
  43. var metaN = createMetaNode(i);
  44. newMetaNodes[i] = metaN;
  45. }
  46. //will contain the new links
  47. newLinks = createInterANDIntraNodes();
  48. //create dataset for the statistic
  49. var metaNodeNames = [];
  50. for (var key in newMetaNodes)
  51. {
  52. //name
  53. var name = "" + key;
  54. metaNodeNames.push(name);
  55. var dataP =
  56. {
  57. label: name,
  58. "Nodes": newMetaNodes[key].amountN,
  59. "IntraEdges": newLinks[key].intraEdges.length,
  60. "InterEdges": newLinks[key].interEdges.length
  61. }
  62. dataset.push(dataP);
  63. }
  64. //create nodes and links for drawing the meta-node graph
  65. var i = 0;
  66. for (var m = 0; m <newLinks.length; m++ )
  67. {
  68. for(var key in newLinks[m])
  69. {
  70. for(var n = 0; n <newLinks[m][key].length; n++ )
  71. {
  72. link = newLinks[m][key][n];
  73. linksD[i] = {source: link.source,
  74. target: link.target
  75. };
  76. i = i+1;
  77. }
  78. }
  79. }
  80. // compute nodes from links data
  81. linksD.forEach(function(link) {
  82. link.source = nodesD[link.source] ||
  83. (nodesD[link.source] = {id: link.source});
  84. link.target = nodesD[link.target] ||
  85. (nodesD[link.target] = {id: link.target});
  86. });
  87. }
  88. /**
  89. * This function creates one metanode to the given community number.
  90. * For every metanode the amount of belonging nodes is calculated and their id's are stored.
  91. * @param {number} communityNr current community number which forms a metanode
  92. * @param {Array} nodes_communitys contains all nodes with its community
  93. * @return {Object} result which contains as properties the overall amount of belonging nodes and an array of ids
  94. * the belonging nodes
  95. */
  96. function createMetaNode(communityNr)
  97. {
  98. amountNode = 0;
  99. belongingNodes = [];
  100. var i = 0;
  101. //for all nodes which have the recent communityNr
  102. for(var nodeId in nodes_communitys) {
  103. var comm = nodes_communitys[nodeId];
  104. if(comm == communityNr)
  105. {
  106. //metanode
  107. belongingNodes.push(nodeId);
  108. amountNode = amountNode +1;
  109. }
  110. i++;
  111. }
  112. var result =
  113. { amountN: amountNode,
  114. belongingN: belongingNodes,
  115. id: communityNr
  116. };
  117. return result;
  118. }
  119. /**
  120. * This function creates inter- and intra-edges according to the current metanode.
  121. * The intra-edges are calculated for every metanode by using the links in the original node and looking through every stored
  122. * node of the metanode, if there is a connection to another node with the same community number. For inter-edges the same
  123. * is done, with the difference that connections to nodes with other communitiy numbers are sought.
  124. * @return {Array} nL is an array of objects which contains the inter- and intra-links of all metanodes.
  125. */
  126. function createInterANDIntraNodes()
  127. { //find according links
  128. var nL = [];
  129. for(var metaNodeId = 0; metaNodeId < newMetaNodes.length; metaNodeId++)
  130. {
  131. var intraEdges = []; //id of edges inside the same community
  132. var interEdges = []; //id of edges to other communities
  133. for(var i = 0; i < newMetaNodes[metaNodeId].belongingN.length; i++)
  134. {
  135. var currentN = newMetaNodes[metaNodeId].belongingN[i];
  136. for(var key in linksOld)
  137. {
  138. if (linksOld[key].source.id == currentN)
  139. {
  140. var target = linksOld[key].target.id;
  141. if(nodesOld[target].module == metaNodeId.toString() )
  142. { //target node belongs to the same community
  143. var edge = {
  144. source: metaNodeId,
  145. target: nodesOld[target].module
  146. };
  147. intraEdges.push(edge);
  148. }else
  149. { //target node belongs to another community
  150. var edge = {
  151. source: metaNodeId,
  152. target: nodesOld[target].module
  153. };
  154. interEdges.push(edge);
  155. }
  156. }
  157. }
  158. }
  159. nL[metaNodeId] = {
  160. intraEdges: intraEdges,
  161. interEdges: interEdges
  162. };
  163. }
  164. return nL;
  165. }
  166. /**
  167. * This functions draws the metanode graph and the statistics. For the metanode graph a d3 force layout is created on a
  168. * svg. The statistic is drawn as grouped bar chart, which is also located on an svg. Also a close button is created to
  169. * delete these two svgs.
  170. */
  171. function drawMetaNodeGraphAndStatistic()
  172. {
  173. var svg = d3.select("svg");
  174. //metaNodeSVG is appended on original svg
  175. var metaNodeSVG = svg.append("svg")
  176. .attr("id", "metaNodeSVG")
  177. .attr("width", svg.attr("width")*0.6) //69%
  178. .attr("height", svg.attr("height"))
  179. .attr("x", svg.attr("width")*0.4)
  180. .classed("metaNodeContainer",true);
  181. //metaNodeSVG is appended on original svg
  182. var statisticSVG = svg.append("svg")
  183. .attr("id", "statisticSVG")
  184. .attr("width", svg.attr("width")*0.4)
  185. .attr("height", svg.attr("height"))
  186. .classed("statisticContainer",true);
  187. drawMetaNodes(metaNodeSVG);
  188. calculateStatistic(statisticSVG);
  189. // Close Button
  190. createMetaNodeCloseButton();
  191. }
  192. /**
  193. * This function uses a given svg to draw a grouped bar chart on it. Here for every metanode the amount of belonging nodes
  194. * is shown, as well as the amount of intra- and inter-edges.
  195. * @param {SVG}statisticSVG contains the SVG in which the grouped bar chart is drawn
  196. */
  197. function calculateStatistic(statisticSVG)
  198. {
  199. //draw black border
  200. statisticSVG.append("rect")
  201. .attr("width", "100%")
  202. .attr("height", "100%")
  203. .attr("fill", "white")
  204. .attr("stroke", "#616161")
  205. .attr("stroke-width", 1)
  206. .attr("fill-opacity", 1)
  207. .attr("stroke-opacity",1);
  208. var attributeNames = d3.keys(dataset[0]).filter(function(key) { return key !== "label"; });
  209. dataset.forEach(function(d) {
  210. d.amounts = attributeNames.map(function(name) { return {name: name, value: +d[name]}; });
  211. });
  212. // drawing
  213. var widthP = statisticSVG.attr("width")/100;
  214. var heightP = statisticSVG.attr("height")/100;
  215. var margin = {top: 10*heightP, right: 10*widthP, bottom: 25*heightP, left: 10*widthP},
  216. width = statisticSVG.attr("width")- margin.left - margin.right,
  217. height = statisticSVG.attr("height") - margin.top - margin.bottom;
  218. var x0 = d3.scale.ordinal()
  219. .rangeRoundBands([margin.left*0.7, width], 0.1);
  220. var x1 = d3.scale.ordinal();
  221. var y = d3.scale.linear()
  222. .range([height, margin.bottom]);
  223. var xAxis = d3.svg.axis()
  224. .scale(x0)
  225. .tickSize(0)
  226. .orient("bottom");
  227. var yAxis = d3.svg.axis()
  228. .scale(y)
  229. .orient("right");
  230. var color = d3.scale.ordinal()
  231. .range(["#e41a1c","#377eb8","#4daf4a"]);
  232. //give names to chart
  233. x0.domain(dataset.map(function(d) { return d.label; }));
  234. x1.domain(attributeNames).rangeRoundBands([0, x0.rangeBand()])
  235. //give data to chart
  236. y.domain([0, d3.max(dataset, function(d) { return d3.max(d.amounts, function(d) { return d.value; }); })]);
  237. statisticSVG.append("g")
  238. .attr("class", "x axis")
  239. .attr("transform", "translate("+margin.left+"," + height + ")") //change here for bar width and location
  240. .call(xAxis);
  241. //create the y-axis and a y-axis text label
  242. statisticSVG.append("g")
  243. .attr("class", "y axis")
  244. .attr("transform", "translate(" + margin.left*0.5 + ", 0)") //change black y-axis here
  245. .call(yAxis)
  246. .append("text")
  247. .attr("x",-13*heightP)
  248. .attr("y", margin.left*0.5)
  249. .attr("transform", "rotate(-90)")
  250. .attr("dy", ".71em")
  251. .style("text-anchor", "end")
  252. .text("Amount");
  253. var bar = statisticSVG.selectAll(".bar")
  254. .data(dataset)
  255. .enter().append("g")
  256. .attr("class", "rect")
  257. .attr("transform", function(d) { return "translate(" +x0(d.label) + ",0)"; });
  258. bar.selectAll("rect")
  259. .data(function(d) { return d.amounts; })
  260. .enter().append("rect")
  261. .attr("width", x1.rangeBand())
  262. .attr("x", function(d) { return x1(d.name); })
  263. .attr("y", function(d) { return y(d.value); })
  264. .attr("value", function(d){return d.name;})
  265. .attr("height", function(d) { return height - y(d.value); })
  266. .style("fill", function(d) { return color(d.name); })
  267. .attr("transform", function(d) { return "translate(" +margin.left + ",0)"; });;
  268. //right legend
  269. var legend = statisticSVG.selectAll(".legend")
  270. .data(attributeNames.slice())
  271. .enter().append("g")
  272. .attr("class", "legend")
  273. .attr("transform", function(d, i) { return "translate(0," + i*20 + ")"; })
  274. legend.append("rect")
  275. .attr("x", width - 5*widthP)
  276. .attr("y", 6.5*heightP)
  277. .attr("width", 18)
  278. .attr("height", 18)
  279. .style("fill", function(d) { return color(d); });
  280. legend.append("text")
  281. .attr("x", width - 8*widthP)
  282. .attr("y", 8*heightP)
  283. .attr("dy", ".35em")
  284. .style("text-anchor", "end")
  285. .text(function(d) { return d; });
  286. }
  287. /**
  288. * This functions draws the metaNode graph on a delivered svg. For every community number a meta-node is drawn with its
  289. * related color. The size of the node is calculated according to the amount of nodes beloning to the certain community
  290. * The strength of the links is dependent on the number of inter-edges to the respective other communities.
  291. * @param {SVG} metaNodeSVG contains the SVG on which the graph will be drawn
  292. */
  293. function drawMetaNodes(metaNodeSVG)
  294. {
  295. var widthP = metaNodeSVG.attr("width")/100;
  296. var heightP = metaNodeSVG.attr("height")/100;
  297. var margin = {top: 0*heightP, right: 0*widthP, bottom: 0*heightP, left: 0*widthP};
  298. var widthM = metaNodeSVG.attr("width")- margin.left - margin.right;
  299. var heightM = metaNodeSVG.attr("height") - margin.top - margin.bottom;
  300. //set background color of metaNodeSVG
  301. metaNodeSVG.append("rect")
  302. .attr("width", "100%")
  303. .attr("height", "100%")
  304. .attr("fill", "white");
  305. //define layout for metaNode-graph
  306. var force = d3.layout.forceInABox()
  307. .groupBy("function (d) {return d.id}")
  308. .gravityOverall(0.5)
  309. .size([widthM, heightM])
  310. .linkStrengthInterCluster(0.5)
  311. .charge(-3000);
  312. force
  313. .nodes(d3.values(nodesD))
  314. .links(linksD)
  315. .on("tick", tick);
  316. // Draw the graph lines
  317. var link = metaNodeSVG.selectAll(".link")
  318. .data(linksD)
  319. .enter().append("line")
  320. .attr("class", "link")
  321. .attr("id", function(d,i){ return "link" + i});
  322. // Draw node as a circle.
  323. var node = metaNodeSVG.selectAll(".node")
  324. .data(force.nodes())
  325. .enter().append("g")
  326. var circle = node.append("circle")
  327. .attr("class", "node")
  328. .attr("id", function(d,i){ return "node" + i});
  329. //calculate attributes of links
  330. var norm = Math.sqrt(Math.pow(width,2) + Math.pow(height,2));
  331. //force.linkDistance(norm/10);
  332. force.linkDistance(norm/7);
  333. //calculate links according to amount of links
  334. link.style("stroke-width", function (d)
  335. {
  336. var percent = 100/linksD.length;
  337. var interEdges = newLinks[d.source.id].interEdges;
  338. var size = 0;
  339. for(var i = 0; i < interEdges.length; i++)
  340. {
  341. if(interEdges[i].target == d.target.id)
  342. {
  343. size = size+1;
  344. }
  345. }
  346. size = size*percent;
  347. return 1+(2*size); //without additional factor
  348. });
  349. //calculate origin radius of nodes
  350. norm = norm/300;
  351. if(norm > 7) norm = 7;
  352. var radius = norm;
  353. //calculate nodes according to amount of nodes in metaNode
  354. var color = d3.scale.category20();//color nodes according to their cluster
  355. circle.attr("r", function(d)
  356. {
  357. var percent = 100/amountMetaN;
  358. var size = (newMetaNodes[d.id].amountN*percent/100); //size is between [0,1]
  359. //calculate radius according to amount of nodes
  360. //return radius + (5*size) //with additional factor
  361. var r = radius + (2*size);
  362. newMetaNodes[d.id]["radius"] = r;
  363. return r;
  364. });
  365. circle.style("fill", function(d){return color(d.id);})
  366. var nodeName = node.append("svg:text")
  367. .attr("class", "text")
  368. .attr("dy", ".35em")
  369. .text(function(d) {return d.id});
  370. force.start();
  371. function tick(e) {
  372. circle.attr("cx", function(d) {
  373. return d.x = Math.max(newMetaNodes[d.id].radius, Math.min(widthM - newMetaNodes[d.id].radius, d.x));
  374. })
  375. .attr("cy", function(d) {
  376. return d.y = Math.max(newMetaNodes[d.id].radius, Math.min(heightM - newMetaNodes[d.id].radius, d.y)); });
  377. nodeName.attr("x", function (d) {return d.x;})
  378. .attr("y", function (d) {return d.y - 4*heightP;});
  379. link.attr("x1", function(d) { return d.source.x; })
  380. .attr("y1", function(d) { return d.source.y; })
  381. .attr("x2", function(d) { return d.target.x; })
  382. .attr("y2", function(d) { return d.target.y; });
  383. }
  384. }
  385. /**
  386. * This function deletes the svgs which store the meta-graph and the statistic. Therefore all existing svgs are located and
  387. * the svgs with the id's "metaNodeSVG" and "metaNodeSVG" are removed. Also the close button is removed.
  388. */
  389. function deleteGraphANDStatistic()
  390. {
  391. var svgs = d3.selectAll("svg")[0];
  392. var flagDelete = [];
  393. for(var i = 0; i <svgs.length; i++)
  394. {
  395. if((svgs[i].id == "metaNodeSVG") || (svgs[i].id == "statisticSVG")) {
  396. flagDelete[i] = 1;
  397. } else
  398. {
  399. flagDelete[i] = 0;
  400. }
  401. }
  402. for(var i = 0; i <svgs.length; i++)
  403. {
  404. if(flagDelete[i] == 1)
  405. {
  406. svgs[i].remove();
  407. }
  408. }
  409. //remove close button
  410. var con = document.getElementById("svgContainer");
  411. var children = con.childNodes;
  412. for(var c = 0; c < children.length; c++) {
  413. if (children[c].id == "closeButton") {
  414. con.removeChild(children[c]);
  415. }
  416. }
  417. }
  418. /**
  419. * This function adds a close button to the container holding the svgs which contain the metanode graph and the statistic.
  420. * This is done by searching this container and appending a button on it. When this button is clicked, the meta-node
  421. * force layout and the grouped bar chart representing the statistic are deleted.
  422. */
  423. function createMetaNodeCloseButton()
  424. {
  425. var MetaOverlay = document.getElementById("svgContainer");
  426. // Button
  427. var closeButton = document.createElement("SPAN");
  428. closeButton.classList.add("w3-button");
  429. closeButton.classList.add("w3-theme-l2");
  430. closeButton.classList.add("w3-display-topright");
  431. closeButton.setAttribute("id", "closeButton");
  432. MetaOverlay.appendChild(closeButton);
  433. // Icon
  434. var closeIcon = document.createElement("SPAN");
  435. closeIcon.classList.add("fa");
  436. closeIcon.classList.add("fa-remove");
  437. closeIcon.classList.add("fa-lg");
  438. closeButton.appendChild(closeIcon);
  439. // Close function
  440. closeButton.addEventListener("click", function() {deleteGraphANDStatistic() });
  441. }