123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- /*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
- var quickSelect = require("./quickSelect");
- /*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
- function Node(axis, data) {
- this.left = null;
- this.right = null;
- this.axis = axis;
- this.data = data;
- }
- /**
- * @constructor
- * @alias module:echarts/data/KDTree
- * @param {Array} points List of points.
- * each point needs an array property to repesent the actual data
- * @param {Number} [dimension]
- * Point dimension.
- * Default will use the first point's length as dimensiont
- */
- var KDTree = function (points, dimension) {
- if (!points.length) {
- return;
- }
- if (!dimension) {
- dimension = points[0].array.length;
- }
- this.dimension = dimension;
- this.root = this._buildTree(points, 0, points.length - 1, 0); // Use one stack to avoid allocation
- // each time searching the nearest point
- this._stack = []; // Again avoid allocating a new array
- // each time searching nearest N points
- this._nearstNList = [];
- };
- /**
- * Resursively build the tree
- */
- KDTree.prototype._buildTree = function (points, left, right, axis) {
- if (right < left) {
- return null;
- }
- var medianIndex = Math.floor((left + right) / 2);
- medianIndex = quickSelect(points, left, right, medianIndex, function (a, b) {
- return a.array[axis] - b.array[axis];
- });
- var median = points[medianIndex];
- var node = new Node(axis, median);
- axis = (axis + 1) % this.dimension;
- if (right > left) {
- node.left = this._buildTree(points, left, medianIndex - 1, axis);
- node.right = this._buildTree(points, medianIndex + 1, right, axis);
- }
- return node;
- };
- /**
- * Find nearest point
- * @param {Array} target Target point
- * @param {Function} squaredDistance Squared distance function
- * @return {Array} Nearest point
- */
- KDTree.prototype.nearest = function (target, squaredDistance) {
- var curr = this.root;
- var stack = this._stack;
- var idx = 0;
- var minDist = Infinity;
- var nearestNode = null;
- if (curr.data !== target) {
- minDist = squaredDistance(curr.data, target);
- nearestNode = curr;
- }
- if (target.array[curr.axis] < curr.data.array[curr.axis]) {
- // Left first
- curr.right && (stack[idx++] = curr.right);
- curr.left && (stack[idx++] = curr.left);
- } else {
- // Right first
- curr.left && (stack[idx++] = curr.left);
- curr.right && (stack[idx++] = curr.right);
- }
- while (idx--) {
- curr = stack[idx];
- var currDist = target.array[curr.axis] - curr.data.array[curr.axis];
- var isLeft = currDist < 0;
- var needsCheckOtherSide = false;
- currDist = currDist * currDist; // Intersecting right hyperplane with minDist hypersphere
- if (currDist < minDist) {
- currDist = squaredDistance(curr.data, target);
- if (currDist < minDist && curr.data !== target) {
- minDist = currDist;
- nearestNode = curr;
- }
- needsCheckOtherSide = true;
- }
- if (isLeft) {
- if (needsCheckOtherSide) {
- curr.right && (stack[idx++] = curr.right);
- } // Search in the left area
- curr.left && (stack[idx++] = curr.left);
- } else {
- if (needsCheckOtherSide) {
- curr.left && (stack[idx++] = curr.left);
- } // Search the right area
- curr.right && (stack[idx++] = curr.right);
- }
- }
- return nearestNode.data;
- };
- KDTree.prototype._addNearest = function (found, dist, node) {
- var nearestNList = this._nearstNList; // Insert to the right position
- // Sort from small to large
- for (var i = found - 1; i > 0; i--) {
- if (dist >= nearestNList[i - 1].dist) {
- break;
- } else {
- nearestNList[i].dist = nearestNList[i - 1].dist;
- nearestNList[i].node = nearestNList[i - 1].node;
- }
- }
- nearestNList[i].dist = dist;
- nearestNList[i].node = node;
- };
- /**
- * Find nearest N points
- * @param {Array} target Target point
- * @param {number} N
- * @param {Function} squaredDistance Squared distance function
- * @param {Array} [output] Output nearest N points
- */
- KDTree.prototype.nearestN = function (target, N, squaredDistance, output) {
- if (N <= 0) {
- output.length = 0;
- return output;
- }
- var curr = this.root;
- var stack = this._stack;
- var idx = 0;
- var nearestNList = this._nearstNList;
- for (var i = 0; i < N; i++) {
- // Allocate
- if (!nearestNList[i]) {
- nearestNList[i] = {};
- }
- nearestNList[i].dist = 0;
- nearestNList[i].node = null;
- }
- var currDist = squaredDistance(curr.data, target);
- var found = 0;
- if (curr.data !== target) {
- found++;
- this._addNearest(found, currDist, curr);
- }
- if (target.array[curr.axis] < curr.data.array[curr.axis]) {
- // Left first
- curr.right && (stack[idx++] = curr.right);
- curr.left && (stack[idx++] = curr.left);
- } else {
- // Right first
- curr.left && (stack[idx++] = curr.left);
- curr.right && (stack[idx++] = curr.right);
- }
- while (idx--) {
- curr = stack[idx];
- var currDist = target.array[curr.axis] - curr.data.array[curr.axis];
- var isLeft = currDist < 0;
- var needsCheckOtherSide = false;
- currDist = currDist * currDist; // Intersecting right hyperplane with minDist hypersphere
- if (found < N || currDist < nearestNList[found - 1].dist) {
- currDist = squaredDistance(curr.data, target);
- if ((found < N || currDist < nearestNList[found - 1].dist) && curr.data !== target) {
- if (found < N) {
- found++;
- }
- this._addNearest(found, currDist, curr);
- }
- needsCheckOtherSide = true;
- }
- if (isLeft) {
- if (needsCheckOtherSide) {
- curr.right && (stack[idx++] = curr.right);
- } // Search in the left area
- curr.left && (stack[idx++] = curr.left);
- } else {
- if (needsCheckOtherSide) {
- curr.left && (stack[idx++] = curr.left);
- } // Search the right area
- curr.right && (stack[idx++] = curr.right);
- }
- } // Copy to output
- for (var i = 0; i < found; i++) {
- output[i] = nearestNList[i].node.data;
- }
- output.length = found;
- return output;
- };
- var _default = KDTree;
- module.exports = _default;
|