sigma.statistics.HITS.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /**
  2. * This plugin computes HITS statistics (Authority and Hub measures) for each node of the graph.
  3. * It adds to the graph model a method called "HITS".
  4. *
  5. * Author: Mehdi El Fadil, Mango Information Systems
  6. * License: This plugin for sigma.js follows the same licensing terms as sigma.js library.
  7. *
  8. * This implementation is based on the original paper J. Kleinberg, Authoritative Sources in a Hyperlinked Environment (http://www.cs.cornell.edu/home/kleinber/auth.pdf), and is inspired by implementation in Gephi software (Patick J. McSweeney <pjmcswee@syr.edu>, Sebastien Heymann <seb@gephi.org>, Dual-licensed under GPL v3 and CDDL)
  9. * https://github.com/Mango-information-systems/gephi/blob/fix-hits/modules/StatisticsPlugin/src/main/java/org/gephi/statistics/plugin/Hits.java
  10. *
  11. * Bugs in Gephi implementation should not be found in this implementation.
  12. * Tests have been put in place based on a test plan used to test implementation in Gephi, cf. discussion here: https://github.com/jacomyal/sigma.js/issues/309
  13. * No guarantee is provided regarding the correctness of the calculations. Plugin author did not control the validity of the test scenarii.
  14. *
  15. * Warning: tricky edge-case. Hubs and authorities for nodes without any edge are only reliable in an undirected graph calculation mode.
  16. *
  17. * Check the code for more information.
  18. *
  19. * Here is how to use it:
  20. *
  21. * > // directed graph
  22. * > var stats = s.graph.HITS()
  23. * > // returns an object indexed by node Id with the authority and hub measures
  24. * > // like { "n0": {"authority": 0.00343, "hub": 0.023975}, "n1": [...]*
  25. *
  26. * > // undirected graph: pass 'true' as function parameter
  27. * > var stats = s.graph.HITS(true)
  28. * > // returns an object indexed by node Id with the authority and hub measures
  29. * > // like { "n0": {"authority": 0.00343, "hub": 0.023975}, "n1": [...]
  30. */
  31. (function() {
  32. 'use strict';
  33. if (typeof sigma === 'undefined')
  34. throw 'sigma is not declared';
  35. /**
  36. * This method takes a graph instance and returns authority and hub measures computed for each node. It uses the built-in
  37. * indexes from sigma's graph model to search in the graph.
  38. *
  39. * @param {boolean} isUndirected flag informing whether the graph is directed or not. Default false: directed graph.
  40. * @return {object} object indexed by node Ids, containing authority and hub measures for each node of the graph.
  41. */
  42. sigma.classes.graph.addMethod(
  43. 'HITS',
  44. function(isUndirected) {
  45. var res = {}
  46. , epsilon = 0.0001
  47. , hubList = []
  48. , authList = []
  49. , nodes = this.nodes()
  50. , nodesCount = nodes.length
  51. , tempRes = {}
  52. if (!isUndirected)
  53. isUndirected = false
  54. for (var i in nodes) {
  55. if (isUndirected) {
  56. hubList.push(nodes[i])
  57. authList.push(nodes[i])
  58. }
  59. else {
  60. if (this.degree(nodes[i].id, 'out') > 0)
  61. hubList.push(nodes[i])
  62. if (this.degree(nodes[i].id, 'in') > 0)
  63. authList.push(nodes[i])
  64. }
  65. res[nodes[i].id] = { authority : 1, hub: 1 }
  66. }
  67. var done
  68. while (true) {
  69. done = true
  70. var authSum = 0
  71. , hubSum = 0
  72. for (var i in authList) {
  73. tempRes[authList[i].id] = {authority : 1, hub:0 }
  74. var connectedNodes = []
  75. if (isUndirected)
  76. connectedNodes = this.allNeighborsIndex[authList[i].id]
  77. else
  78. connectedNodes = this.inNeighborsIndex[authList[i].id]
  79. for (var j in connectedNodes) {
  80. if (j != authList[i].id)
  81. tempRes[authList[i].id].authority += res[j].hub
  82. }
  83. authSum += tempRes[authList[i].id].authority
  84. }
  85. for (var i in hubList) {
  86. if (tempRes[hubList[i].id])
  87. tempRes[hubList[i].id].hub = 1
  88. else
  89. tempRes[hubList[i].id] = {authority: 0, hub : 1 }
  90. var connectedNodes = []
  91. if (isUndirected)
  92. connectedNodes = this.allNeighborsIndex[hubList[i].id]
  93. else
  94. connectedNodes = this.outNeighborsIndex[hubList[i].id]
  95. for (var j in connectedNodes) {
  96. if (j != hubList[i].id)
  97. tempRes[hubList[i].id].hub += res[j].authority
  98. }
  99. hubSum += tempRes[hubList[i].id].hub
  100. }
  101. for (var i in authList) {
  102. tempRes[authList[i].id].authority /= authSum
  103. if (Math.abs((tempRes[authList[i].id].authority - res[authList[i].id].authority) / res[authList[i].id].authority) >= epsilon)
  104. done = false
  105. }
  106. for (var i in hubList) {
  107. tempRes[hubList[i].id].hub /= hubSum
  108. if (Math.abs((tempRes[hubList[i].id].hub - res[hubList[i].id].hub) / res[hubList[i].id].hub) >= epsilon)
  109. done = false
  110. }
  111. res = tempRes
  112. tempRes = {}
  113. if (done)
  114. break
  115. }
  116. return res
  117. }
  118. )
  119. }).call(window)