|  | /* | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | import quickSelect from './quickSelect'; | 
|  | import { VectorArray } from 'zrender/src/core/vector'; | 
|  |  | 
|  | type KDTreePoint = { | 
|  | array: VectorArray | 
|  | }; | 
|  |  | 
|  | class KDTreeNode<T> { | 
|  |  | 
|  | left: KDTreeNode<T>; | 
|  | right: KDTreeNode<T>; | 
|  |  | 
|  | axis: number; | 
|  | data: T; | 
|  |  | 
|  | constructor(axis: number, data: T) { | 
|  | 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 represent the actual data | 
|  | * @param {Number} [dimension] | 
|  | *        Point dimension. | 
|  | *        Default will use the first point's length as dimension. | 
|  | */ | 
|  |  | 
|  | class KDTree<T extends KDTreePoint> { | 
|  |  | 
|  | dimension: number; | 
|  | root: KDTreeNode<T>; | 
|  |  | 
|  | // Use one stack to avoid allocation | 
|  | // each time searching the nearest point | 
|  | private _stack: KDTreeNode<T>[] = []; | 
|  | // Again avoid allocating a new array | 
|  | // each time searching nearest N points | 
|  | private _nearstNList: { | 
|  | dist: number, | 
|  | node: KDTreeNode<T> | 
|  | }[] = []; | 
|  |  | 
|  | constructor(points: T[], dimension?: number) { | 
|  | if (!points.length) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!dimension) { | 
|  | dimension = points[0].array.length; | 
|  | } | 
|  | this.dimension = dimension; | 
|  | this.root = this._buildTree(points, 0, points.length - 1, 0); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Recursively build the tree. | 
|  | */ | 
|  | private _buildTree(points: T[], left: number, right: number, axis: number): KDTreeNode<T> { | 
|  | if (right < left) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | let medianIndex = Math.floor((left + right) / 2); | 
|  | medianIndex = quickSelect( | 
|  | points, left, right, medianIndex, | 
|  | function (a: T, b: T) { | 
|  | return a.array[axis] - b.array[axis]; | 
|  | } | 
|  | ); | 
|  | const median = points[medianIndex]; | 
|  |  | 
|  | const node = new KDTreeNode(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  target Target point | 
|  | * @param  squaredDistance Squared distance function | 
|  | * @return Nearest point | 
|  | */ | 
|  | nearest(target: T, squaredDistance: (a: T, b: T) => number) { | 
|  | let curr = this.root; | 
|  | const stack = this._stack; | 
|  | let idx = 0; | 
|  | let minDist = Infinity; | 
|  | let 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]; | 
|  | let currDist = target.array[curr.axis] - curr.data.array[curr.axis]; | 
|  | const isLeft = currDist < 0; | 
|  | let 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; | 
|  | }; | 
|  |  | 
|  | _addNearest(found: number, dist: number, node: KDTreeNode<T>) { | 
|  | const nearestNList = this._nearstNList; | 
|  | let i = found - 1; | 
|  | // Insert to the right position | 
|  | // Sort from small to large | 
|  | for (; 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  target Target point | 
|  | * @param  N | 
|  | * @param  squaredDistance Squared distance function | 
|  | * @param  output Output nearest N points | 
|  | */ | 
|  | nearestN( | 
|  | target: T, | 
|  | N: number, | 
|  | squaredDistance: (a: T, b: T) => number, | 
|  | output: T[] | 
|  | ) { | 
|  | if (N <= 0) { | 
|  | output.length = 0; | 
|  | return output; | 
|  | } | 
|  |  | 
|  | let curr = this.root; | 
|  | const stack = this._stack; | 
|  | let idx = 0; | 
|  |  | 
|  | const nearestNList = this._nearstNList; | 
|  | for (let i = 0; i < N; i++) { | 
|  | // Allocate | 
|  | if (!nearestNList[i]) { | 
|  | nearestNList[i] = { | 
|  | dist: 0, node: null | 
|  | }; | 
|  | } | 
|  | nearestNList[i].dist = 0; | 
|  | nearestNList[i].node = null; | 
|  | } | 
|  | const currDist = squaredDistance(curr.data, target); | 
|  |  | 
|  | let 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]; | 
|  | let currDist = target.array[curr.axis] - curr.data.array[curr.axis]; | 
|  | const isLeft = currDist < 0; | 
|  | let 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 (let i = 0; i < found; i++) { | 
|  | output[i] = nearestNList[i].node.data; | 
|  | } | 
|  | output.length = found; | 
|  |  | 
|  | return output; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  |  | 
|  | export default KDTree; |