0e8c8209fae99c388302586f5fda7eee064e9175.svn-base 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. /**
  2. * K-Dimension Tree
  3. *
  4. * @module echarts/data/KDTree
  5. * @author Yi Shen(https://github.com/pissang)
  6. */
  7. define(function (require) {
  8. var quickSelect = require('./quickSelect');
  9. function Node(axis, data) {
  10. this.left = null;
  11. this.right = null;
  12. this.axis = axis;
  13. this.data = data;
  14. }
  15. /**
  16. * @constructor
  17. * @alias module:echarts/data/KDTree
  18. * @param {Array} points List of points.
  19. * each point needs an array property to repesent the actual data
  20. * @param {Number} [dimension]
  21. * Point dimension.
  22. * Default will use the first point's length as dimensiont
  23. */
  24. var KDTree = function (points, dimension) {
  25. if (!points.length) {
  26. return;
  27. }
  28. if (!dimension) {
  29. dimension = points[0].array.length;
  30. }
  31. this.dimension = dimension;
  32. this.root = this._buildTree(points, 0, points.length - 1, 0);
  33. // Use one stack to avoid allocation
  34. // each time searching the nearest point
  35. this._stack = [];
  36. // Again avoid allocating a new array
  37. // each time searching nearest N points
  38. this._nearstNList = [];
  39. };
  40. /**
  41. * Resursively build the tree
  42. */
  43. KDTree.prototype._buildTree = function (points, left, right, axis) {
  44. if (right < left) {
  45. return null;
  46. }
  47. var medianIndex = Math.floor((left + right) / 2);
  48. medianIndex = quickSelect(
  49. points, left, right, medianIndex,
  50. function (a, b) {
  51. return a.array[axis] - b.array[axis];
  52. }
  53. );
  54. var median = points[medianIndex];
  55. var node = new Node(axis, median);
  56. axis = (axis + 1) % this.dimension;
  57. if (right > left) {
  58. node.left = this._buildTree(points, left, medianIndex - 1, axis);
  59. node.right = this._buildTree(points, medianIndex + 1, right, axis);
  60. }
  61. return node;
  62. };
  63. /**
  64. * Find nearest point
  65. * @param {Array} target Target point
  66. * @param {Function} squaredDistance Squared distance function
  67. * @return {Array} Nearest point
  68. */
  69. KDTree.prototype.nearest = function (target, squaredDistance) {
  70. var curr = this.root;
  71. var stack = this._stack;
  72. var idx = 0;
  73. var minDist = Infinity;
  74. var nearestNode = null;
  75. if (curr.data !== target) {
  76. minDist = squaredDistance(curr.data, target);
  77. nearestNode = curr;
  78. }
  79. if (target.array[curr.axis] < curr.data.array[curr.axis]) {
  80. // Left first
  81. curr.right && (stack[idx++] = curr.right);
  82. curr.left && (stack[idx++] = curr.left);
  83. }
  84. else {
  85. // Right first
  86. curr.left && (stack[idx++] = curr.left);
  87. curr.right && (stack[idx++] = curr.right);
  88. }
  89. while (idx--) {
  90. curr = stack[idx];
  91. var currDist = target.array[curr.axis] - curr.data.array[curr.axis];
  92. var isLeft = currDist < 0;
  93. var needsCheckOtherSide = false;
  94. currDist = currDist * currDist;
  95. // Intersecting right hyperplane with minDist hypersphere
  96. if (currDist < minDist) {
  97. currDist = squaredDistance(curr.data, target);
  98. if (currDist < minDist && curr.data !== target) {
  99. minDist = currDist;
  100. nearestNode = curr;
  101. }
  102. needsCheckOtherSide = true;
  103. }
  104. if (isLeft) {
  105. if (needsCheckOtherSide) {
  106. curr.right && (stack[idx++] = curr.right);
  107. }
  108. // Search in the left area
  109. curr.left && (stack[idx++] = curr.left);
  110. }
  111. else {
  112. if (needsCheckOtherSide) {
  113. curr.left && (stack[idx++] = curr.left);
  114. }
  115. // Search the right area
  116. curr.right && (stack[idx++] = curr.right);
  117. }
  118. }
  119. return nearestNode.data;
  120. };
  121. KDTree.prototype._addNearest = function (found, dist, node) {
  122. var nearestNList = this._nearstNList;
  123. // Insert to the right position
  124. // Sort from small to large
  125. for (var i = found - 1; i > 0; i--) {
  126. if (dist >= nearestNList[i - 1].dist) {
  127. break;
  128. }
  129. else {
  130. nearestNList[i].dist = nearestNList[i - 1].dist;
  131. nearestNList[i].node = nearestNList[i - 1].node;
  132. }
  133. }
  134. nearestNList[i].dist = dist;
  135. nearestNList[i].node = node;
  136. };
  137. /**
  138. * Find nearest N points
  139. * @param {Array} target Target point
  140. * @param {number} N
  141. * @param {Function} squaredDistance Squared distance function
  142. * @param {Array} [output] Output nearest N points
  143. */
  144. KDTree.prototype.nearestN = function (target, N, squaredDistance, output) {
  145. if (N <= 0) {
  146. output.length = 0;
  147. return output;
  148. }
  149. var curr = this.root;
  150. var stack = this._stack;
  151. var idx = 0;
  152. var nearestNList = this._nearstNList;
  153. for (var i = 0; i < N; i++) {
  154. // Allocate
  155. if (!nearestNList[i]) {
  156. nearestNList[i] = {};
  157. }
  158. nearestNList[i].dist = 0;
  159. nearestNList[i].node = null;
  160. }
  161. var currDist = squaredDistance(curr.data, target);
  162. var found = 0;
  163. if (curr.data !== target) {
  164. found++;
  165. this._addNearest(found, currDist, curr);
  166. }
  167. if (target.array[curr.axis] < curr.data.array[curr.axis]) {
  168. // Left first
  169. curr.right && (stack[idx++] = curr.right);
  170. curr.left && (stack[idx++] = curr.left);
  171. }
  172. else {
  173. // Right first
  174. curr.left && (stack[idx++] = curr.left);
  175. curr.right && (stack[idx++] = curr.right);
  176. }
  177. while (idx--) {
  178. curr = stack[idx];
  179. var currDist = target.array[curr.axis] - curr.data.array[curr.axis];
  180. var isLeft = currDist < 0;
  181. var needsCheckOtherSide = false;
  182. currDist = currDist * currDist;
  183. // Intersecting right hyperplane with minDist hypersphere
  184. if (found < N || currDist < nearestNList[found - 1].dist) {
  185. currDist = squaredDistance(curr.data, target);
  186. if (
  187. (found < N || currDist < nearestNList[found - 1].dist)
  188. && curr.data !== target
  189. ) {
  190. if (found < N) {
  191. found++;
  192. }
  193. this._addNearest(found, currDist, curr);
  194. }
  195. needsCheckOtherSide = true;
  196. }
  197. if (isLeft) {
  198. if (needsCheckOtherSide) {
  199. curr.right && (stack[idx++] = curr.right);
  200. }
  201. // Search in the left area
  202. curr.left && (stack[idx++] = curr.left);
  203. }
  204. else {
  205. if (needsCheckOtherSide) {
  206. curr.left && (stack[idx++] = curr.left);
  207. }
  208. // Search the right area
  209. curr.right && (stack[idx++] = curr.right);
  210. }
  211. }
  212. // Copy to output
  213. for (var i = 0; i < found; i++) {
  214. output[i] = nearestNList[i].node.data;
  215. }
  216. output.length = found;
  217. return output;
  218. };
  219. return KDTree;
  220. });